diff --git a/.azurepipelines/preview.yml b/.azurepipelines/preview.yml index 5693e201c..aafb51e28 100644 --- a/.azurepipelines/preview.yml +++ b/.azurepipelines/preview.yml @@ -34,10 +34,10 @@ jobs: value: '.azurepipelines/signlist${{parameters.config}}.txt' steps: - task: UseDotNet@2 - displayName: 'Install .NET 6.0' + displayName: 'Install .NET 8.0' inputs: packageType: 'sdk' - version: '6.0.x' + version: '8.0.x' includePreviewVersions: false - task: DownloadSecureFile@1 name: strongnamefile diff --git a/.azurepipelines/signlistDebug.txt b/.azurepipelines/signlistDebug.txt index cf6e91d62..d408a09fd 100644 --- a/.azurepipelines/signlistDebug.txt +++ b/.azurepipelines/signlistDebug.txt @@ -2,38 +2,48 @@ Stack\Opc.Ua.Core\bin\Debug\netstandard2.0\Opc.Ua.Core.dll Stack\Opc.Ua.Core\bin\Debug\netstandard2.1\Opc.Ua.Core.dll Stack\Opc.Ua.Core\bin\Debug\net48\Opc.Ua.Core.dll Stack\Opc.Ua.Core\bin\Debug\net6.0\Opc.Ua.Core.dll +Stack\Opc.Ua.Core\bin\Debug\net8.0\Opc.Ua.Core.dll Stack\Opc.Ua.Bindings.Https\bin\Debug\netstandard2.0\Opc.Ua.Bindings.Https.dll Stack\Opc.Ua.Bindings.Https\bin\Debug\netcoreapp3.1\Opc.Ua.Bindings.Https.dll Stack\Opc.Ua.Bindings.Https\bin\Debug\net48\Opc.Ua.Bindings.Https.dll Stack\Opc.Ua.Bindings.Https\bin\Debug\net6.0\Opc.Ua.Bindings.Https.dll +Stack\Opc.Ua.Bindings.Https\bin\Debug\net8.0\Opc.Ua.Bindings.Https.dll Libraries\Opc.Ua.Server\bin\Debug\netstandard2.0\Opc.Ua.Server.dll Libraries\Opc.Ua.Server\bin\Debug\netstandard2.1\Opc.Ua.Server.dll Libraries\Opc.Ua.Server\bin\Debug\net48\Opc.Ua.Server.dll Libraries\Opc.Ua.Server\bin\Debug\net6.0\Opc.Ua.Server.dll +Libraries\Opc.Ua.Server\bin\Debug\net8.0\Opc.Ua.Server.dll Libraries\Opc.Ua.Client\bin\Debug\netstandard2.0\Opc.Ua.Client.dll Libraries\Opc.Ua.Client\bin\Debug\netstandard2.1\Opc.Ua.Client.dll Libraries\Opc.Ua.Client\bin\Debug\net48\Opc.Ua.Client.dll Libraries\Opc.Ua.Client\bin\Debug\net6.0\Opc.Ua.Client.dll +Libraries\Opc.Ua.Client\bin\Debug\net8.0\Opc.Ua.Client.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Debug\netstandard2.1\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Debug\net48\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Debug\net6.0\Opc.Ua.Client.ComplexTypes.dll +Libraries\Opc.Ua.Client.ComplexTypes\bin\Debug\net8.0\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Configuration\bin\Debug\netstandard2.0\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Configuration\bin\Debug\netstandard2.1\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Configuration\bin\Debug\net48\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Configuration\bin\Debug\net6.0\Opc.Ua.Configuration.dll +Libraries\Opc.Ua.Configuration\bin\Debug\net8.0\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Debug\netstandard2.0\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Debug\netstandard2.1\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Debug\net48\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Debug\net6.0\Opc.Ua.Gds.Client.Common.dll +Libraries\Opc.Ua.Gds.Client.Common\bin\Debug\net8.0\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Debug\netstandard2.0\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Debug\netstandard2.1\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Debug\net48\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Debug\net6.0\Opc.Ua.Gds.Server.Common.dll +Libraries\Opc.Ua.Gds.Server.Common\bin\Debug\net8.0\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Security.Certificates\bin\Debug\netstandard2.0\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.Security.Certificates\bin\Debug\netstandard2.1\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.Security.Certificates\bin\Debug\net48\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.Security.Certificates\bin\Debug\net6.0\Opc.Ua.Security.Certificates.dll +Libraries\Opc.Ua.Security.Certificates\bin\Debug\net8.0\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.PubSub\bin\Debug\netstandard2.0\Opc.Ua.PubSub.dll Libraries\Opc.Ua.PubSub\bin\Debug\netstandard2.1\Opc.Ua.PubSub.dll Libraries\Opc.Ua.PubSub\bin\Debug\net48\Opc.Ua.PubSub.dll Libraries\Opc.Ua.PubSub\bin\Debug\net6.0\Opc.Ua.PubSub.dll +Libraries\Opc.Ua.PubSub\bin\Debug\net8.0\Opc.Ua.PubSub.dll diff --git a/.azurepipelines/signlistRelease.txt b/.azurepipelines/signlistRelease.txt index 096da79e4..6ec934498 100644 --- a/.azurepipelines/signlistRelease.txt +++ b/.azurepipelines/signlistRelease.txt @@ -2,38 +2,48 @@ Stack\Opc.Ua.Core\bin\Release\netstandard2.0\Opc.Ua.Core.dll Stack\Opc.Ua.Core\bin\Release\netstandard2.1\Opc.Ua.Core.dll Stack\Opc.Ua.Core\bin\Release\net48\Opc.Ua.Core.dll Stack\Opc.Ua.Core\bin\Release\net6.0\Opc.Ua.Core.dll +Stack\Opc.Ua.Core\bin\Release\net8.0\Opc.Ua.Core.dll Stack\Opc.Ua.Bindings.Https\bin\Release\netstandard2.0\Opc.Ua.Bindings.Https.dll Stack\Opc.Ua.Bindings.Https\bin\Release\netcoreapp3.1\Opc.Ua.Bindings.Https.dll Stack\Opc.Ua.Bindings.Https\bin\Release\net48\Opc.Ua.Bindings.Https.dll Stack\Opc.Ua.Bindings.Https\bin\Release\net6.0\Opc.Ua.Bindings.Https.dll +Stack\Opc.Ua.Bindings.Https\bin\Release\net8.0\Opc.Ua.Bindings.Https.dll Libraries\Opc.Ua.Server\bin\Release\netstandard2.0\Opc.Ua.Server.dll Libraries\Opc.Ua.Server\bin\Release\netstandard2.1\Opc.Ua.Server.dll Libraries\Opc.Ua.Server\bin\Release\net48\Opc.Ua.Server.dll Libraries\Opc.Ua.Server\bin\Release\net6.0\Opc.Ua.Server.dll +Libraries\Opc.Ua.Server\bin\Release\net8.0\Opc.Ua.Server.dll Libraries\Opc.Ua.Client\bin\Release\netstandard2.0\Opc.Ua.Client.dll Libraries\Opc.Ua.Client\bin\Release\netstandard2.1\Opc.Ua.Client.dll Libraries\Opc.Ua.Client\bin\Release\net48\Opc.Ua.Client.dll Libraries\Opc.Ua.Client\bin\Release\net6.0\Opc.Ua.Client.dll +Libraries\Opc.Ua.Client\bin\Release\net8.0\Opc.Ua.Client.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Release\netstandard2.1\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Release\net48\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Release\net6.0\Opc.Ua.Client.ComplexTypes.dll +Libraries\Opc.Ua.Client.ComplexTypes\bin\Release\net8.0\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Configuration\bin\Release\netstandard2.0\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Configuration\bin\Release\netstandard2.1\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Configuration\bin\Release\net48\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Configuration\bin\Release\net6.0\Opc.Ua.Configuration.dll +Libraries\Opc.Ua.Configuration\bin\Release\net8.0\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Release\netstandard2.0\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Release\netstandard2.1\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Release\net48\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Release\net6.0\Opc.Ua.Gds.Client.Common.dll +Libraries\Opc.Ua.Gds.Client.Common\bin\Release\net8.0\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Release\netstandard2.0\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Release\netstandard2.1\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Release\net48\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Release\net6.0\Opc.Ua.Gds.Server.Common.dll +Libraries\Opc.Ua.Gds.Server.Common\bin\Release\net8.0\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Security.Certificates\bin\Release\netstandard2.0\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.Security.Certificates\bin\Release\netstandard2.1\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.Security.Certificates\bin\Release\net48\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.Security.Certificates\bin\Release\net6.0\Opc.Ua.Security.Certificates.dll +Libraries\Opc.Ua.Security.Certificates\bin\Release\net8.0\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.PubSub\bin\Release\netstandard2.0\Opc.Ua.PubSub.dll Libraries\Opc.Ua.PubSub\bin\Release\netstandard2.1\Opc.Ua.PubSub.dll Libraries\Opc.Ua.PubSub\bin\Release\net48\Opc.Ua.PubSub.dll Libraries\Opc.Ua.PubSub\bin\Release\net6.0\Opc.Ua.PubSub.dll +Libraries\Opc.Ua.PubSub\bin\Release\net8.0\Opc.Ua.PubSub.dll diff --git a/.azurepipelines/test.yml b/.azurepipelines/test.yml index 178d3e8ef..2eb67f2cd 100644 --- a/.azurepipelines/test.yml +++ b/.azurepipelines/test.yml @@ -6,6 +6,7 @@ parameters: framework: net6.0 agents: '@{}' jobnamesuffix: '' + customtestarget: '' jobs: - job: testprep${{ parameters.jobnamesuffix }} displayName: Prepare Test Jobs ${{ parameters.configuration }} (${{ parameters.framework }}) @@ -30,21 +31,25 @@ jobs: variables: DOTNET_CLI_TELEMETRY_OPTOUT: true DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + CustomTestTarget: ${{ parameters.customtestarget }} + ${{ if eq( parameters.customtestarget, '' ) }}: + DotCliCommandline: '--framework ${{ parameters.framework }}' + ${{ else }}: + DotCliCommandline: '/p:CustomTestTarget=${{ parameters.customtestarget }}' pool: vmImage: $(poolImage) steps: - task: UseDotNet@2 - displayName: 'Install .NET Core 3.1' - condition: eq('${{parameters.framework}}', 'netcoreapp3.1') + displayName: 'Install .NET 6.0' + condition: eq('${{parameters.framework}}', 'net6.0') inputs: packageType: 'sdk' - version: '3.1.x' + version: '6.0.x' - task: UseDotNet@2 - displayName: 'Install .NET 6.0' - condition: or(eq('${{parameters.framework}}', 'netcoreapp3.1'), and(eq('${{parameters.framework}}', 'net6.0'), ne(variables['poolImage'], 'windows-2022'))) + displayName: 'Install .NET 8.0' inputs: packageType: 'sdk' - version: '6.0.x' + version: '8.0.x' - task: NuGetToolInstaller@1 inputs: versionSpec: '>=5.8.x' @@ -58,11 +63,11 @@ jobs: inputs: command: restore projects: '**/*.Tests.csproj' - arguments: '--framework ${{ parameters.framework }} --configuration ${{ parameters.configuration }}' + arguments: '${{ variables.DotCliCommandline }} --configuration ${{ parameters.configuration }}' - task: DotNetCoreCLI@2 displayName: Test ${{ parameters.configuration }} timeoutInMinutes: 45 inputs: command: test projects: '**/*.Tests.csproj' - arguments: '--no-restore --framework ${{ parameters.framework }} --configuration ${{ parameters.configuration }}' + arguments: '--no-restore ${{ variables.DotCliCommandline }} --configuration ${{ parameters.configuration }}' diff --git a/.azurepipelines/testcc.yml b/.azurepipelines/testcc.yml index 7cc539aee..601f7192c 100644 --- a/.azurepipelines/testcc.yml +++ b/.azurepipelines/testcc.yml @@ -7,6 +7,7 @@ parameters: agent: 'linux' poolImage: 'ubuntu-22.04' jobnamesuffix: 'net60cc' + customtestarget: '' jobs: - job: testcc${{ parameters.jobnamesuffix }} displayName: Coverage ${{ parameters.agent }} ${{ parameters.configuration }} @@ -14,16 +15,25 @@ jobs: DOTNET_CLI_TELEMETRY_OPTOUT: true DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true disable.coverage.autogenerate: true + CustomTestTarget: ${{ parameters.customtestarget }} + ${{ if eq( parameters.customtestarget, '' ) }}: + DotCliCommandline: '--framework ${{ parameters.framework }}' + ${{ else }}: + DotCliCommandline: '/p:CustomTestTarget=${{ parameters.customtestarget }}' pool: vmImage: ${{ parameters.poolImage }} steps: - task: UseDotNet@2 displayName: 'Install .NET 6.0' - condition: and(eq('${{parameters.framework}}', 'net6.0'), ne(variables['poolImage'], 'windows-2022')) + condition: eq('${{parameters.framework}}', 'net6.0') inputs: packageType: 'sdk' version: '6.0.x' - includePreviewVersions: true + - task: UseDotNet@2 + displayName: 'Install .NET 8.0' + inputs: + packageType: 'sdk' + version: '8.0.x' - task: NuGetToolInstaller@1 inputs: versionSpec: '>=5.8.x' @@ -37,7 +47,7 @@ jobs: inputs: command: restore projects: 'UA Core Library.sln' - arguments: '--framework ${{ parameters.framework }} --configuration ${{ parameters.configuration }}' + arguments: '${{ variables.DotCliCommandline }} --configuration ${{ parameters.configuration }}' - task: DotNetCoreCLI@2 displayName: Test ${{ parameters.framework }} timeoutInMinutes: 45 @@ -45,7 +55,7 @@ jobs: command: test projects: 'UA Core Library.sln' # note: /p:CollectCoverage=true is only used to disable deterministc builds - arguments: '--no-restore --framework ${{ parameters.framework }} --configuration ${{ parameters.configuration }} /p:CollectCoverage=true --collect:"XPlat Code Coverage" --settings ./Tests/coverlet.runsettings.xml --results-directory $(Agent.TempDirectory)' + arguments: '--no-restore ${{ variables.DotCliCommandline }} --configuration ${{ parameters.configuration }} /p:CollectCoverage=true --collect:"XPlat Code Coverage" --settings ./Tests/coverlet.runsettings.xml --results-directory $(Agent.TempDirectory)' publishTestResults: false - script: | bash <(curl -s https://codecov.io/bash) -s $(Agent.TempDirectory) diff --git a/.editorconfig b/.editorconfig index 8beb94ddb..6de1ac41a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -324,3 +324,13 @@ dotnet_diagnostic.IDE0049.severity = # CA1507: Use nameof in place of string dotnet_diagnostic.CA1507.severity = warning +# exclude generated code +[**/Generated/*.cs] +generated_code = true +dotnet_diagnostic.severity = none +dotnet_analyzer.severity = none + +[*.{Classes,DataTypes,Constants}.cs] +generated_code = true +dotnet_diagnostic.severity = none +dotnet_analyzer.severity = none diff --git a/.github/workflows/buildandtest.yml b/.github/workflows/buildandtest.yml index f7c5132a0..bb9f510aa 100644 --- a/.github/workflows/buildandtest.yml +++ b/.github/workflows/buildandtest.yml @@ -1,4 +1,4 @@ -name: Build and Test .NET 6.0 +name: Build and Test .NET 8.0 on: push: @@ -25,9 +25,10 @@ jobs: os: [ubuntu-latest, windows-latest, macOS-latest] csproj: [Security.Certificates, Core, Server, Client, Client.ComplexTypes, PubSub, Configuration, Gds] include: - - framework: 'net6.0' - dotnet-version: '6.0.x' + - framework: 'net8.0' + dotnet-version: '8.0.x' configuration: 'Release' + customtesttarget: net8.0 env: OS: ${{ matrix.os }} @@ -38,12 +39,12 @@ jobs: TESTRESULTS: "TestResults-${{matrix.csproj}}-${{matrix.os}}-${{matrix.framework}}-${{matrix.configuration}}" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET ${{ matrix.dotnet-version }} - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ matrix.dotnet-version }} @@ -52,11 +53,11 @@ jobs: run: ./.azurepipelines/set-version.ps1 - name: Build - run: dotnet build ${{ env.CSPROJECT }} --force --framework ${{ matrix.framework }} --configuration ${{ matrix.configuration }} + run: dotnet build ${{ env.CSPROJECT }} --force --framework ${{ matrix.framework }} --configuration ${{ matrix.configuration }} /p:CustomTestTarget=${{ matrix.customtesttarget }} - name: Test # note: /p:CollectCoverage=true is only used to disable deterministc builds - run: dotnet test ${{ env.CSPROJECT }} --no-build --framework ${{ matrix.framework }} --logger trx --configuration ${{ matrix.configuration }} /p:CollectCoverage=true --collect:"XPlat Code Coverage" --settings ./Tests/coverlet.runsettings.xml --results-directory ${{ env.TESTRESULTS }} + run: dotnet test ${{ env.CSPROJECT }} --no-build --framework ${{ matrix.framework }} --logger trx --configuration ${{ matrix.configuration }} /p:CollectCoverage=true /p:CustomTestTarget=${{ matrix.customtesttarget }} --collect:"XPlat Code Coverage" --settings ./Tests/coverlet.runsettings.xml --results-directory ${{ env.TESTRESULTS }} - name: Upload test results uses: actions/upload-artifact@v3 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index fe35d1000..0983b3aa3 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL @@ -45,9 +45,9 @@ jobs: # queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Set up .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: - dotnet-version: '6.x' + dotnet-version: '8.x' # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild - name: Setup MSBuild.exe diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 06189f7fc..12fbe6ead 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -34,14 +34,14 @@ jobs: id-token: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up .NET 6 - uses: actions/setup-dotnet@v3 + - name: Set up .NET 8 + uses: actions/setup-dotnet@v4 with: - dotnet-version: '6.x' + dotnet-version: '8.x' - name: Set Version shell: pwsh @@ -57,13 +57,13 @@ jobs: # https://github.com/docker/build-push-action - name: Setup Docker buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # Login against a Docker registry except on PR # https://github.com/docker/login-action - name: Log into registry ${{ env.REGISTRY }} if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -93,18 +93,18 @@ jobs: # https://github.com/docker/metadata-action - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: ${{ env.IMAGE_REPOSITORY }} - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 # Build and push Docker image with Buildx (don't push on PR) # https://github.com/docker/build-push-action - name: Build and push Docker image id: build-and-push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . build-args: | @@ -113,7 +113,7 @@ jobs: InformationalVersion=${{ env.NBGV_AssemblyInformationalVersion }} push: ${{ github.event_name != 'pull_request' }} tags: ${{ env.TAG_BRANCH }},${{ env.TAG_LATEST }}${{ env.TAG_RELEASE }} - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64,linux/arm64/v8 file: ./Applications/ConsoleReferenceServer/Dockerfile labels: ${{ steps.meta.outputs.labels }} provenance: false diff --git a/Applications/ClientControls.Net4/ClientUtils.cs b/Applications/ClientControls.Net4/ClientUtils.cs index 87de94feb..525ca6c8e 100644 --- a/Applications/ClientControls.Net4/ClientUtils.cs +++ b/Applications/ClientControls.Net4/ClientUtils.cs @@ -281,9 +281,9 @@ public static string GetAttributeDisplayText(Session session, uint attributeId, } // check for byte strings. - if (value.Value is byte[]) + if (value.Value is byte[] byteArray) { - return Utils.ToHexString(value.Value as byte[]); + return Utils.ToHexString(byteArray); } // use default format. diff --git a/Applications/ClientControls.Net4/Common/Client/EditComplexValueCtrl.cs b/Applications/ClientControls.Net4/Common/Client/EditComplexValueCtrl.cs index 06b72364c..413619734 100644 --- a/Applications/ClientControls.Net4/Common/Client/EditComplexValueCtrl.cs +++ b/Applications/ClientControls.Net4/Common/Client/EditComplexValueCtrl.cs @@ -246,9 +246,8 @@ public void SetArraySize() TypeInfo currentType = info.TypeInfo; object currentValue = info.Value; - if (info.Value is Variant) + if (info.Value is Variant variant) { - Variant variant = (Variant)info.Value; currentValue = variant.Value; if (currentValue != null) @@ -395,9 +394,8 @@ public void SetType(BuiltInType builtInType) currentValue = TypeInfo.GetDefaultValue(currentType.BuiltInType); } - if (info.Value is Variant) + if (info.Value is Variant variant) { - Variant variant = (Variant)info.Value; currentValue = variant.Value; if (currentValue != null) @@ -680,9 +678,8 @@ private void ShowValueNoNotify(AccessInfo parent) TypeInfo typeInfo = parent.TypeInfo; object value = parent.Value; - if (value is Variant) + if (value is Variant variant) { - Variant variant = (Variant)value; value = variant.Value; if (value != null) @@ -797,58 +794,58 @@ private void ShowValueNoNotify(AccessInfo parent) } // check for XmlElements. - if (structure is XmlElement) + if (structure is XmlElement xmlElement) { - ShowTextValue((XmlElement)structure); + ShowTextValue((XmlElement)xmlElement); return; } // check for ByteString. - if (structure is byte[]) + if (structure is byte[] byteString) { - ShowTextValue((byte[])structure); + ShowTextValue(byteString); return; } // check for NodeId. - if (structure is NodeId) + if (structure is NodeId nodeId) { - ShowTextValue(((NodeId)structure).ToString()); + ShowTextValue(nodeId.ToString()); return; } // check for ExpandedNodeId. - if (structure is ExpandedNodeId) + if (structure is ExpandedNodeId expandedNodeId) { - ShowTextValue(((ExpandedNodeId)structure).ToString()); + ShowTextValue(expandedNodeId.ToString()); return; } // check for QualifiedName. - if (structure is QualifiedName) + if (structure is QualifiedName qualifiedName) { - ShowTextValue(((QualifiedName)structure).ToString()); + ShowTextValue(qualifiedName.ToString()); return; } // check for Guid. - if (structure is Guid) + if (structure is Guid guid) { - ShowTextValue(((Guid)structure).ToString()); + ShowTextValue(guid.ToString()); return; } // check for Uuid. - if (structure is Uuid) + if (structure is Uuid uuid) { - ShowTextValue(((Uuid)structure).ToString()); + ShowTextValue(uuid.ToString()); return; } // check for StatusCode. - if (structure is StatusCode) + if (structure is StatusCode statusCode) { - ShowTextValue(Utils.Format("0x{0:X8}", ((StatusCode)structure).Code)); + ShowTextValue(Utils.Format("0x{0:X8}", statusCode.Code)); return; } @@ -1008,15 +1005,13 @@ private Type GetDataType(AccessInfo accessInfo) if (accessInfo.Parent != null) { - if (accessInfo.Parent.Value is Array) + if (accessInfo.Parent.Value is Array array) { - Array array = (Array)accessInfo.Parent.Value; return array.GetType().GetElementType(); } - if (accessInfo.Parent.Value is IList) + if (accessInfo.Parent.Value is IList list) { - IList list = (IList)accessInfo.Parent.Value; return GetListElementType(list); } } @@ -1179,9 +1174,8 @@ private string ValueToString(object value, TypeInfo typeInfo) return String.Empty; } - if (value is Variant) + if (value is Variant variant) { - Variant variant = (Variant)value; value = variant.Value; if (value != null) diff --git a/Applications/ClientControls.Net4/Configuration/Common (OLD)/ClipboardHack.cs b/Applications/ClientControls.Net4/Configuration/Common (OLD)/ClipboardHack.cs index bafaae01e..53dd5d791 100644 --- a/Applications/ClientControls.Net4/Configuration/Common (OLD)/ClipboardHack.cs +++ b/Applications/ClientControls.Net4/Configuration/Common (OLD)/ClipboardHack.cs @@ -146,7 +146,7 @@ private static void SetClipboardPrivate() #endregion #region Private Fields - private static object m_lock = new object(); + private static readonly object m_lock = new object(); private static string m_format = null; private static object m_data = null; private static Exception m_error = null; diff --git a/Applications/ClientControls.Net4/Endpoints/UsernameTokenDlg.cs b/Applications/ClientControls.Net4/Endpoints/UsernameTokenDlg.cs index f630083bf..30a6f1c9b 100644 --- a/Applications/ClientControls.Net4/Endpoints/UsernameTokenDlg.cs +++ b/Applications/ClientControls.Net4/Endpoints/UsernameTokenDlg.cs @@ -73,7 +73,7 @@ public bool ShowDialog(UserNameIdentityToken token) if (token.Password != null && token.Password.Length > 0) { - PasswordTB.Text = new UTF8Encoding().GetString(token.Password); + PasswordTB.Text = Encoding.UTF8.GetString(token.Password); } } @@ -86,7 +86,7 @@ public bool ShowDialog(UserNameIdentityToken token) if (!String.IsNullOrEmpty(PasswordTB.Text)) { - token.Password = new UTF8Encoding().GetBytes(PasswordTB.Text); + token.Password = Encoding.UTF8.GetBytes(PasswordTB.Text); } else { diff --git a/Applications/ConsoleReferenceClient/ClientSamples.cs b/Applications/ConsoleReferenceClient/ClientSamples.cs index 8c8efc8a0..4aee8c0a7 100644 --- a/Applications/ConsoleReferenceClient/ClientSamples.cs +++ b/Applications/ConsoleReferenceClient/ClientSamples.cs @@ -376,7 +376,7 @@ public void SubscribeToDataChanges(ISession session, uint minLifeTime) /// Adds the root node to the result. /// Filters nodes from namespace 0 from the result. /// The list of nodes on the server. - public IList FetchAllNodesNodeCache( + public async Task> FetchAllNodesNodeCacheAsync( IUAClient uaClient, NodeId startingNode, bool fetchTree = false, @@ -398,13 +398,13 @@ public IList FetchAllNodesNodeCache( { // clear NodeCache to fetch all nodes from server uaClient.Session.NodeCache.Clear(); - FetchReferenceIdTypes(uaClient.Session); + await FetchReferenceIdTypesAsync(uaClient.Session).ConfigureAwait(false); } // add root node if (addRootNode) { - var rootNode = uaClient.Session.NodeCache.Find(startingNode); + var rootNode = await uaClient.Session.NodeCache.FindAsync(startingNode).ConfigureAwait(false); nodeDictionary[rootNode.NodeId] = rootNode; } @@ -419,11 +419,11 @@ public IList FetchAllNodesNodeCache( searchDepth++; Utils.LogInfo("{0}: Find {1} references after {2}ms", searchDepth, nodesToBrowse.Count, stopwatch.ElapsedMilliseconds); - IList response = uaClient.Session.NodeCache.FindReferences( + IList response = await uaClient.Session.NodeCache.FindReferencesAsync( nodesToBrowse, references, false, - true); + true).ConfigureAwait(false); var nextNodesToBrowse = new ExpandedNodeIdCollection(); int duplicates = 0; @@ -466,7 +466,7 @@ public IList FetchAllNodesNodeCache( } else { - nodeDictionary[node.NodeId] = node; ; + nodeDictionary[node.NodeId] = node; } } else @@ -511,10 +511,11 @@ public IList FetchAllNodesNodeCache( /// The UAClient with a session to use. /// The node where the browse operation starts. /// An optional BrowseDescription to use. - public ReferenceDescriptionCollection BrowseFullAddressSpace( + public async Task BrowseFullAddressSpaceAsync( IUAClient uaClient, NodeId startingNode = null, - BrowseDescription browseDescription = null) + BrowseDescription browseDescription = null, + CancellationToken ct = default) { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); @@ -563,9 +564,10 @@ public ReferenceDescriptionCollection BrowseFullAddressSpace( repeatBrowse = false; try { - _ = uaClient.Session.Browse(null, null, - kMaxReferencesPerNode, browseCollection, - out browseResultCollection, out diagnosticsInfoCollection); + var browseResponse = await uaClient.Session.BrowseAsync(null, null, + kMaxReferencesPerNode, browseCollection, ct).ConfigureAwait(false); + browseResultCollection = browseResponse.Results; + diagnosticsInfoCollection = browseResponse.DiagnosticInfos; ClientBase.ValidateResponse(browseResultCollection, browseCollection); ClientBase.ValidateDiagnosticInfos(diagnosticsInfoCollection, browseCollection); @@ -629,8 +631,9 @@ public ReferenceDescriptionCollection BrowseFullAddressSpace( } Utils.LogInfo("BrowseNext {0} continuation points.", continuationPoints.Count); - _ = uaClient.Session.BrowseNext(null, false, continuationPoints, - out var browseNextResultCollection, out diagnosticsInfoCollection); + var browseNextResult = await uaClient.Session.BrowseNextAsync(null, false, continuationPoints, ct).ConfigureAwait(false); + var browseNextResultCollection = browseNextResult.Results; + diagnosticsInfoCollection = browseNextResult.DiagnosticInfos; ClientBase.ValidateResponse(browseNextResultCollection, continuationPoints); ClientBase.ValidateDiagnosticInfos(diagnosticsInfoCollection, continuationPoints); allBrowseResults.AddRange(browseNextResultCollection); @@ -696,7 +699,7 @@ public ReferenceDescriptionCollection BrowseFullAddressSpace( /// Outputs elapsed time information for perf testing and lists all /// types that were successfully added to the session encodeable type factory. /// - public async Task LoadTypeSystem(ISession session) + public async Task LoadTypeSystemAsync(ISession session) { m_output.WriteLine("Load the server type system."); @@ -742,7 +745,7 @@ public async Task LoadTypeSystem(ISession session) /// The NodeCache needs this information to function properly with subtypes of hierarchical calls. /// /// The session to use - void FetchReferenceIdTypes(ISession session) + Task FetchReferenceIdTypesAsync(ISession session) { // fetch the reference types first, otherwise browse for e.g. hierarchical references with subtypes won't work var bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public; @@ -750,7 +753,7 @@ void FetchReferenceIdTypes(ISession session) var referenceTypes = typeof(ReferenceTypeIds) .GetFields(bindingFlags) .Select(field => NodeId.ToExpandedNodeId((NodeId)field.GetValue(null), namespaceUris)); - session.FetchTypeTree(new ExpandedNodeIdCollection(referenceTypes)); + return session.FetchTypeTreeAsync(new ExpandedNodeIdCollection(referenceTypes)); } #endregion @@ -897,7 +900,7 @@ public async Task SubscribeAllValuesAsync( StartNodeId = item.NodeId, AttributeId = Attributes.Value, SamplingInterval = samplingInterval, - DisplayName = item.DisplayName.Text ?? item.BrowseName.Name, + DisplayName = item.DisplayName?.Text ?? item.BrowseName.Name, QueueSize = queueSize, DiscardOldest = true, MonitoringMode = MonitoringMode.Reporting, @@ -907,7 +910,7 @@ public async Task SubscribeAllValuesAsync( } // Create the monitored items on Server side - subscription.ApplyChanges(); + await subscription.ApplyChangesAsync().ConfigureAwait(false); m_output.WriteLine("MonitoredItems {0} created for SubscriptionId = {1}.", subscription.MonitoredItemCount, subscription.Id); } catch (Exception ex) diff --git a/Applications/ConsoleReferenceClient/ConsoleReferenceClient.csproj b/Applications/ConsoleReferenceClient/ConsoleReferenceClient.csproj index d7a11680b..79e7956c5 100644 --- a/Applications/ConsoleReferenceClient/ConsoleReferenceClient.csproj +++ b/Applications/ConsoleReferenceClient/ConsoleReferenceClient.csproj @@ -24,9 +24,9 @@ - - - + + + diff --git a/Applications/ConsoleReferenceClient/Program.cs b/Applications/ConsoleReferenceClient/Program.cs index e586a373f..16450caf6 100644 --- a/Applications/ConsoleReferenceClient/Program.cs +++ b/Applications/ConsoleReferenceClient/Program.cs @@ -129,8 +129,7 @@ public static async Task Main(string[] args) // Define the UA Client application ApplicationInstance.MessageDlg = new ApplicationMessageDlg(output); CertificatePasswordProvider PasswordProvider = new CertificatePasswordProvider(password); - ApplicationInstance application = new ApplicationInstance - { + ApplicationInstance application = new ApplicationInstance { ApplicationName = applicationName, ApplicationType = ApplicationType.Client, ConfigSectionName = configSectionName, @@ -220,7 +219,7 @@ public static async Task Main(string[] args) var samples = new ClientSamples(output, ClientBase.ValidateResponse, quitEvent, verbose); if (loadTypes) { - await samples.LoadTypeSystem(uaClient.Session).ConfigureAwait(false); + await samples.LoadTypeSystemAsync(uaClient.Session).ConfigureAwait(false); } if (browseall || fetchall || jsonvalues) @@ -230,7 +229,7 @@ public static async Task Main(string[] args) if (browseall) { referenceDescriptions = - samples.BrowseFullAddressSpace(uaClient, Objects.RootFolder); + await samples.BrowseFullAddressSpaceAsync(uaClient, Objects.RootFolder).ConfigureAwait(false); variableIds = new NodeIdCollection(referenceDescriptions .Where(r => r.NodeClass == NodeClass.Variable && r.TypeDefinition.NamespaceIndex != 0) .Select(r => ExpandedNodeId.ToNodeId(r.NodeId, uaClient.Session.NamespaceUris))); @@ -239,8 +238,7 @@ public static async Task Main(string[] args) IList allNodes = null; if (fetchall) { - allNodes = samples.FetchAllNodesNodeCache( - uaClient, Objects.RootFolder, true, true, false); + allNodes = await samples.FetchAllNodesNodeCacheAsync(uaClient, Objects.RootFolder, true, true, false).ConfigureAwait(false); variableIds = new NodeIdCollection(allNodes .Where(r => r.NodeClass == NodeClass.Variable && r is VariableNode && ((VariableNode)r).DataType.NamespaceIndex != 0) .Select(r => ExpandedNodeId.ToNodeId(r.NodeId, uaClient.Session.NamespaceUris))); @@ -248,7 +246,7 @@ public static async Task Main(string[] args) if (jsonvalues && variableIds != null) { - await samples.ReadAllValuesAsync(uaClient, variableIds).ConfigureAwait(false); + var (allValues, results) = await samples.ReadAllValuesAsync(uaClient, variableIds).ConfigureAwait(false); } if (subscribe && (browseall || fetchall)) diff --git a/Applications/ConsoleReferenceClient/UAClient.cs b/Applications/ConsoleReferenceClient/UAClient.cs index cb1190c50..51c49193e 100644 --- a/Applications/ConsoleReferenceClient/UAClient.cs +++ b/Applications/ConsoleReferenceClient/UAClient.cs @@ -99,7 +99,7 @@ public void Dispose() /// /// The reconnect period to be used in ms. /// - public int ReconnectPeriod { get; set; } = 5000; + public int ReconnectPeriod { get; set; } = 1000; /// /// The reconnect period exponential backoff to be used in ms. @@ -178,11 +178,7 @@ public async Task ConnectAsync(string serverUrl, bool useSecurity = true, EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration); ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration); -#if NET6_0_OR_GREATER var sessionFactory = TraceableSessionFactory.Instance; -#else - var sessionFactory = DefaultSessionFactory.Instance; -#endif // Create the session var session = await sessionFactory.CreateAsync( @@ -274,7 +270,7 @@ private void Session_KeepAlive(ISession session, KeepAliveEventArgs e) try { // check for events from discarded sessions. - if (!Object.ReferenceEquals(session, m_session)) + if (!m_session.Equals(session)) { return; } @@ -298,6 +294,9 @@ private void Session_KeepAlive(ISession session, KeepAliveEventArgs e) Utils.LogInfo("KeepAlive status {0}, reconnect status {1}.", e.Status, state); } + // cancel sending a new keep alive request, because reconnect is triggered. + e.CancelKeepAlive = true; + return; } } @@ -380,7 +379,7 @@ protected virtual void CertificateValidation(CertificateValidator sender, Certif #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private ReverseConnectManager m_reverseConnectManager; private ApplicationConfiguration m_configuration; private SessionReconnectHandler m_reconnectHandler; diff --git a/Applications/ConsoleReferencePublisher/Program.cs b/Applications/ConsoleReferencePublisher/Program.cs index af4c7cf18..fb5d4781e 100644 --- a/Applications/ConsoleReferencePublisher/Program.cs +++ b/Applications/ConsoleReferencePublisher/Program.cs @@ -61,7 +61,7 @@ public static void Main(string[] args) { "u|udp_uadp", "Use UDP with UADP encoding Profile", v => useUdpUadp = v != null }, { "url|publisher_url=", "Publisher Url Address", v => publisherUrl = v}, }; - + try { IList extraArgs = options.Parse(args); @@ -125,7 +125,7 @@ public static void Main(string[] args) // Create configuration using MQTT protocol and JSON Encoding pubSubConfiguration = CreatePublisherConfiguration_MqttJson(publisherUrl); Console.WriteLine("The PubSub Connection was initialized using MQTT & JSON Profile."); - } + } } // Create the UA Publisher application using configuration file @@ -226,7 +226,7 @@ private static PubSubConfigurationDataType CreatePublisherConfiguration_UdpUadp( dataSetWriter1.DataSetFieldContentMask = (uint)DataSetFieldContentMask.RawData; dataSetWriter1.DataSetName = "Simple"; dataSetWriter1.KeyFrameCount = 1; - UadpDataSetWriterMessageDataType uadpDataSetWriterMessage = new UadpDataSetWriterMessageDataType() { + UadpDataSetWriterMessageDataType uadpDataSetWriterMessage = new UadpDataSetWriterMessageDataType() { NetworkMessageNumber = 1, DataSetMessageContentMask = (uint)(UadpDataSetMessageContentMask.Status | UadpDataSetMessageContentMask.SequenceNumber), }; @@ -242,7 +242,7 @@ private static PubSubConfigurationDataType CreatePublisherConfiguration_UdpUadp( dataSetWriter2.DataSetFieldContentMask = (uint)DataSetFieldContentMask.RawData; dataSetWriter2.DataSetName = "AllTypes"; dataSetWriter2.KeyFrameCount = 1; - uadpDataSetWriterMessage = new UadpDataSetWriterMessageDataType() { + uadpDataSetWriterMessage = new UadpDataSetWriterMessageDataType() { NetworkMessageNumber = 1, DataSetMessageContentMask = (uint)(UadpDataSetMessageContentMask.Status | UadpDataSetMessageContentMask.SequenceNumber), }; @@ -301,7 +301,7 @@ private static PubSubConfigurationDataType CreatePublisherConfiguration_MqttJson string brokerMetaData = "$Metadata"; #region Define WriterGroup1 - Json - + WriterGroupDataType writerGroup1 = new WriterGroupDataType(); writerGroup1.Name = "WriterGroup 1"; writerGroup1.Enabled = true; @@ -369,8 +369,7 @@ private static PubSubConfigurationDataType CreatePublisherConfiguration_MqttJson }; dataSetWriter2.MessageSettings = new ExtensionObject(jsonDataSetWriterMessage); - jsonDataSetWriterTransport = new BrokerDataSetWriterTransportDataType() - { + jsonDataSetWriterTransport = new BrokerDataSetWriterTransportDataType() { QueueName = brokerQueueName, RequestedDeliveryGuarantee = BrokerTransportQualityOfService.BestEffort, MetaDataQueueName = $"{brokerQueueName}/{brokerMetaData}", diff --git a/Applications/ConsoleReferencePublisher/PublishedValuesWrites.cs b/Applications/ConsoleReferencePublisher/PublishedValuesWrites.cs index fee710264..1c2acc093 100644 --- a/Applications/ConsoleReferencePublisher/PublishedValuesWrites.cs +++ b/Applications/ConsoleReferencePublisher/PublishedValuesWrites.cs @@ -309,8 +309,7 @@ private void IncrementValue(FieldMetaData variable, ushort namespaceIndex, long } else if (variable.ValueRank == ValueRanks.OneDimension) { - uint[] values = dataValue.Value as uint[]; - if (values != null) + if (dataValue.Value is uint[] values) { for (int i = 0; i < values.Length; i++) { diff --git a/Applications/ConsoleReferenceServer/ConsoleReferenceServer.csproj b/Applications/ConsoleReferenceServer/ConsoleReferenceServer.csproj index 914c63b60..28c81fb76 100644 --- a/Applications/ConsoleReferenceServer/ConsoleReferenceServer.csproj +++ b/Applications/ConsoleReferenceServer/ConsoleReferenceServer.csproj @@ -30,12 +30,12 @@ - - + + - - - + + + diff --git a/Applications/ConsoleReferenceServer/Dockerfile b/Applications/ConsoleReferenceServer/Dockerfile index 24bc58665..45ba81180 100644 --- a/Applications/ConsoleReferenceServer/Dockerfile +++ b/Applications/ConsoleReferenceServer/Dockerfile @@ -1,7 +1,7 @@ -FROM mcr.microsoft.com/dotnet/runtime:6.0-bullseye-slim AS base +FROM mcr.microsoft.com/dotnet/runtime:8.0-jammy AS base WORKDIR /app -FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0-jammy AS build WORKDIR /src # copy csproj and restore as distinct layers @@ -12,16 +12,18 @@ COPY ["Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.cspro COPY ["Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj", "Libraries/Opc.Ua.Configuration/"] COPY ["Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj", "Libraries/Opc.Ua.Server/"] COPY ["Applications/Quickstarts.Servers/Quickstarts.Servers.csproj", "Applications/Quickstarts.Servers/"] -RUN dotnet restore "Applications/ConsoleReferenceServer/ConsoleReferenceServer.csproj" --force -p:NoHttps=true +ENV DOTNET_EnableWriteXorExecute=0 +RUN dotnet restore "Applications/ConsoleReferenceServer/ConsoleReferenceServer.csproj" --force -p:NoHttps=true -p:CustomTestTarget=net8.0 COPY . . WORKDIR "/src/Applications/ConsoleReferenceServer" ARG Version ARG SimpleVersion ARG InformationalVersion -ENV OPTIONS="-c Release -f net6.0 -p:NoHttps=true -p:Dockerbuild=true -p:Version=${Version} -p:AssemblyVersion=${SimpleVersion} -p:FileVersion=${Version} -p:InformationalVersion=${InformationalVersion}" +ENV OPTIONS="-c Release -f net8.0 -p:NoHttps=true -p:Dockerbuild=true -p:Version=${Version} -p:AssemblyVersion=${SimpleVersion} -p:FileVersion=${Version} -p:InformationalVersion=${InformationalVersion} -p:CustomTestTarget=net8.0" FROM build AS publish +ENV DOTNET_EnableWriteXorExecute=0 RUN dotnet publish "ConsoleReferenceServer.csproj" --no-restore ${OPTIONS} -o /app/publish FROM base AS final diff --git a/Applications/ConsoleReferenceSubscriber/Program.cs b/Applications/ConsoleReferenceSubscriber/Program.cs index 7e76faeb5..db5c9abfe 100644 --- a/Applications/ConsoleReferenceSubscriber/Program.cs +++ b/Applications/ConsoleReferenceSubscriber/Program.cs @@ -535,7 +535,7 @@ private static PubSubConfigurationDataType CreateSubscriberConfiguration_MqttJso dataSetReaderAllTypes.Enabled = true; dataSetReaderAllTypes.DataSetFieldContentMask = (uint)DataSetFieldContentMask.RawData;// RawData encoding; dataSetReaderAllTypes.KeyFrameCount = 1; - + jsonDataSetReaderMessage = new JsonDataSetReaderMessageDataType() { NetworkMessageContentMask = (uint)(JsonNetworkMessageContentMask.NetworkMessageHeader | JsonNetworkMessageContentMask.DataSetMessageHeader diff --git a/Applications/Quickstarts.Servers/Alarms/AlarmHolders/AlarmConditionTypeHolder.cs b/Applications/Quickstarts.Servers/Alarms/AlarmHolders/AlarmConditionTypeHolder.cs index 45dd04876..616c60ba4 100644 --- a/Applications/Quickstarts.Servers/Alarms/AlarmHolders/AlarmConditionTypeHolder.cs +++ b/Applications/Quickstarts.Servers/Alarms/AlarmHolders/AlarmConditionTypeHolder.cs @@ -200,9 +200,9 @@ protected override bool GetRetainState() return retainState; } - protected override void SetActive(BaseEventState baseEvent, bool activeState) + protected override void SetActive(BaseEventState state, bool activeState) { - AlarmConditionState alarm = GetAlarm(baseEvent); + AlarmConditionState alarm = GetAlarm(state); alarm.SetActiveState(SystemContext, activeState); } diff --git a/Applications/Quickstarts.Servers/Alarms/AlarmNodeManager.cs b/Applications/Quickstarts.Servers/Alarms/AlarmNodeManager.cs index 9c0b29494..52ed5b532 100644 --- a/Applications/Quickstarts.Servers/Alarms/AlarmNodeManager.cs +++ b/Applications/Quickstarts.Servers/Alarms/AlarmNodeManager.cs @@ -577,7 +577,11 @@ private AlarmHolder GetAlarmHolder(NodeId node) string unmodifiedName = node.Identifier.ToString(); // This is bad, but I'm not sure why the NodeName is being attached with an underscore, it messes with this lookup. +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER + string name = unmodifiedName.Replace("Alarms_", "Alarms.", StringComparison.Ordinal); +#else string name = unmodifiedName.Replace("Alarms_", "Alarms."); +#endif string mapName = name; if (name.EndsWith(AlarmDefines.TRIGGER_EXTENSION) || name.EndsWith(AlarmDefines.ALARM_EXTENSION)) @@ -663,7 +667,11 @@ public string GetSourceNameFromNodeId(NodeId nodeId) // Alarms.UnitName.AnalogSource if (splitString.Length >= 2) { +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER + sourceName = splitString[splitString.Length - 1].Replace("Source", "", StringComparison.Ordinal); +#else sourceName = splitString[splitString.Length - 1].Replace("Source", ""); +#endif } } diff --git a/Applications/Quickstarts.Servers/Boiler/Boiler.NodeSet2.xml b/Applications/Quickstarts.Servers/Boiler/Boiler.NodeSet2.xml index f5e210b37..8e764d624 100644 --- a/Applications/Quickstarts.Servers/Boiler/Boiler.NodeSet2.xml +++ b/Applications/Quickstarts.Servers/Boiler/Boiler.NodeSet2.xml @@ -1,10 +1,10 @@  - + http://opcfoundation.org/UA/Boiler/ - + diff --git a/Applications/Quickstarts.Servers/Boiler/Boiler.Types.xsd b/Applications/Quickstarts.Servers/Boiler/Boiler.Types.xsd index 4c321c10a..e2c6d129f 100644 --- a/Applications/Quickstarts.Servers/Boiler/Boiler.Types.xsd +++ b/Applications/Quickstarts.Servers/Boiler/Boiler.Types.xsd @@ -7,7 +7,7 @@ > - + diff --git a/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBuffer.NodeSet2.xml b/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBuffer.NodeSet2.xml index 92343e315..537c3a2a1 100644 --- a/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBuffer.NodeSet2.xml +++ b/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBuffer.NodeSet2.xml @@ -1,10 +1,10 @@  - + http://samples.org/UA/MemoryBuffer - + diff --git a/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBuffer.Types.xsd b/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBuffer.Types.xsd index 599f40e5c..58dd18637 100644 --- a/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBuffer.Types.xsd +++ b/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBuffer.Types.xsd @@ -7,7 +7,7 @@ > - + diff --git a/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBufferState.cs b/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBufferState.cs index 4f16b8f79..a1f9f83c9 100644 --- a/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBufferState.cs +++ b/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBufferState.cs @@ -678,7 +678,7 @@ void PublishTimer_Tick(object sender, EventArgs e) #endregion #region Private Fields - private object m_dataLock = new object(); + private readonly object m_dataLock = new object(); private IServerInternal m_server; private INodeManager m_nodeManager; private MemoryBufferMonitoredItem[][] m_monitoringTable; diff --git a/Applications/Quickstarts.Servers/SampleNodeManager/DataChangeMonitoredItem.cs b/Applications/Quickstarts.Servers/SampleNodeManager/DataChangeMonitoredItem.cs index b91b4a916..c03661639 100644 --- a/Applications/Quickstarts.Servers/SampleNodeManager/DataChangeMonitoredItem.cs +++ b/Applications/Quickstarts.Servers/SampleNodeManager/DataChangeMonitoredItem.cs @@ -837,7 +837,7 @@ private void Publish( #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private MonitoredNode m_source; private ISubscription m_subscription; private uint m_id; diff --git a/Applications/Quickstarts.Servers/SampleNodeManager/SampleNodeManager.cs b/Applications/Quickstarts.Servers/SampleNodeManager/SampleNodeManager.cs index c7721dcc1..3b94e5d02 100644 --- a/Applications/Quickstarts.Servers/SampleNodeManager/SampleNodeManager.cs +++ b/Applications/Quickstarts.Servers/SampleNodeManager/SampleNodeManager.cs @@ -3088,7 +3088,7 @@ protected virtual void OnSetMonitoringMode( #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private IServerInternal m_server; private ServerSystemContext m_systemContext; private IList m_namespaceUris; diff --git a/Applications/Quickstarts.Servers/TestData/ArrayValueObjectState.cs b/Applications/Quickstarts.Servers/TestData/ArrayValueObjectState.cs index 872d27566..18df5a97a 100644 --- a/Applications/Quickstarts.Servers/TestData/ArrayValueObjectState.cs +++ b/Applications/Quickstarts.Servers/TestData/ArrayValueObjectState.cs @@ -74,6 +74,9 @@ protected override void OnAfterCreate(ISystemContext context, NodeState node) InitializeVariable(context, IntegerValue, TestData.Variables.ArrayValueObjectType_IntegerValue); InitializeVariable(context, UIntegerValue, TestData.Variables.ArrayValueObjectType_UIntegerValue); InitializeVariable(context, VectorValue, TestData.Variables.ArrayValueObjectType_VectorValue); + InitializeVariable(context, VectorUnionValue, TestData.Variables.ArrayValueObjectType_VectorUnionValue); + InitializeVariable(context, VectorWithOptionalFieldsValue, TestData.Variables.ArrayValueObjectType_VectorWithOptionalFieldsValue); + InitializeVariable(context, MultipleVectorsValue, TestData.Variables.ArrayValueObjectType_MultipleVectorsValue); } #endregion @@ -123,6 +126,9 @@ protected override ServiceResult OnGenerateValues( GenerateValue(system, IntegerValue); GenerateValue(system, UIntegerValue); GenerateValue(system, VectorValue); + GenerateValue(system, VectorUnionValue); + GenerateValue(system, VectorWithOptionalFieldsValue); + GenerateValue(system, MultipleVectorsValue); return base.OnGenerateValues(context, method, objectId, count); } diff --git a/Applications/Quickstarts.Servers/TestData/HistoryArchive.cs b/Applications/Quickstarts.Servers/TestData/HistoryArchive.cs index f91fbf7f0..e54c6afbe 100644 --- a/Applications/Quickstarts.Servers/TestData/HistoryArchive.cs +++ b/Applications/Quickstarts.Servers/TestData/HistoryArchive.cs @@ -190,7 +190,7 @@ private void OnUpdate(object state) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private Timer m_updateTimer; private Dictionary m_records; #endregion diff --git a/Applications/Quickstarts.Servers/TestData/HistoryFile.cs b/Applications/Quickstarts.Servers/TestData/HistoryFile.cs index ace1d7560..33eff0c66 100644 --- a/Applications/Quickstarts.Servers/TestData/HistoryFile.cs +++ b/Applications/Quickstarts.Servers/TestData/HistoryFile.cs @@ -146,7 +146,7 @@ public DataValue NextRaw(DateTime lastTime, bool isForward, bool isReadModified, #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private List m_entries; #endregion } diff --git a/Applications/Quickstarts.Servers/TestData/NodeStateComparer.cs b/Applications/Quickstarts.Servers/TestData/NodeStateComparer.cs index da849b6a0..2de893c86 100644 --- a/Applications/Quickstarts.Servers/TestData/NodeStateComparer.cs +++ b/Applications/Quickstarts.Servers/TestData/NodeStateComparer.cs @@ -59,14 +59,14 @@ public bool Equals(NodeState x, NodeState y) } /// - public int GetHashCode(NodeState node) + public int GetHashCode(NodeState obj) { - if (ReferenceEquals(node, null)) + if (ReferenceEquals(obj, null)) { return 0; } - return node.NodeId.GetHashCode(); + return obj.NodeId.GetHashCode(); } } } diff --git a/Applications/Quickstarts.Servers/TestData/ScalarValueObjectState.cs b/Applications/Quickstarts.Servers/TestData/ScalarValueObjectState.cs index b444abae2..f8aa7127a 100644 --- a/Applications/Quickstarts.Servers/TestData/ScalarValueObjectState.cs +++ b/Applications/Quickstarts.Servers/TestData/ScalarValueObjectState.cs @@ -74,6 +74,9 @@ protected override void OnAfterCreate(ISystemContext context, NodeState node) InitializeVariable(context, IntegerValue, TestData.Variables.ScalarValueObjectType_IntegerValue); InitializeVariable(context, UIntegerValue, TestData.Variables.ScalarValueObjectType_UIntegerValue); InitializeVariable(context, VectorValue, TestData.Variables.ScalarValueObjectType_VectorValue); + InitializeVariable(context, VectorUnionValue, TestData.Variables.ScalarValueObjectType_VectorUnionValue); + InitializeVariable(context, VectorWithOptionalFieldsValue, TestData.Variables.ScalarValueObjectType_VectorWithOptionalFieldsValue); + InitializeVariable(context, MultipleVectorsValue, TestData.Variables.ScalarValueObjectType_MultipleVectorsValue); } #endregion @@ -123,6 +126,9 @@ protected override ServiceResult OnGenerateValues( GenerateValue(system, IntegerValue); GenerateValue(system, UIntegerValue); GenerateValue(system, VectorValue); + GenerateValue(system, VectorUnionValue); + GenerateValue(system, VectorWithOptionalFieldsValue); + GenerateValue(system, MultipleVectorsValue); return base.OnGenerateValues(context, method, objectId, count); } diff --git a/Applications/Quickstarts.Servers/TestData/TestData.Classes.cs b/Applications/Quickstarts.Servers/TestData/TestData.Classes.cs index 038c82f0c..65452619b 100644 --- a/Applications/Quickstarts.Servers/TestData/TestData.Classes.cs +++ b/Applications/Quickstarts.Servers/TestData/TestData.Classes.cs @@ -4606,7 +4606,7 @@ protected override void InitializeOptionalChildren(ISystemContext context) #region Initialization String private const string InitializationString = "AQAAABgAAABodHRwOi8vdGVzdC5vcmcvVUEvRGF0YS//////BGCAAgEAAAABAB0AAABTY2FsYXJWYWx1" + - "ZU9iamVjdFR5cGVJbnN0YW5jZQEBXAQBAVwEXAQAAAEAAAAAJAABAWAEHwAAADVgiQoCAAAAAQAQAAAA" + + "ZU9iamVjdFR5cGVJbnN0YW5jZQEBXAQBAVwEXAQAAAEAAAAAJAABAWAEIgAAADVgiQoCAAAAAQAQAAAA" + "U2ltdWxhdGlvbkFjdGl2ZQEBXQQDAAAAAEcAAABJZiB0cnVlIHRoZSBzZXJ2ZXIgd2lsbCBwcm9kdWNl" + "IG5ldyB2YWx1ZXMgZm9yIGVhY2ggbW9uaXRvcmVkIHZhcmlhYmxlLgAuAERdBAAAAAH/////AQH/////" + "AAAAAARhggoEAAAAAQAOAAAAR2VuZXJhdGVWYWx1ZXMBAV4EAC8BAfkDXgQAAAEB/////wEAAAAXYKkK" + @@ -4673,7 +4673,10 @@ protected override void InitializeOptionalChildren(ISystemContext context) "Z2VyVmFsdWUBAbUEAC8AP7UEAAAAHP////8BAf////8AAAAAFWCJCgIAAAABAAsAAABWZWN0b3JWYWx1" + "ZQEBtgQALwEBYQe2BAAAAQFgB/////8BAf////8DAAAAFWCJCgIAAAABAAEAAABYAQG3BAAuAES3BAAA" + "AAv/////AQH/////AAAAABVgiQoCAAAAAQABAAAAWQEBuAQALgBEuAQAAAAL/////wEB/////wAAAAAV" + - "YIkKAgAAAAEAAQAAAFoBAbkEAC4ARLkEAAAAC/////8BAf////8AAAAA"; + "YIkKAgAAAAEAAQAAAFoBAbkEAC4ARLkEAAAAC/////8BAf////8AAAAAFWCJCgIAAAABABAAAABWZWN0" + + "b3JVbmlvblZhbHVlAQH+DQAvAD/+DQAAAQEADv////8BAf////8AAAAAFWCJCgIAAAABAB0AAABWZWN0" + + "b3JXaXRoT3B0aW9uYWxGaWVsZHNWYWx1ZQEB/w0ALwA//w0AAAEBAQ7/////AQH/////AAAAABVgiQoC" + + "AAAAAQAUAAAATXVsdGlwbGVWZWN0b3JzVmFsdWUBAR4OAC8APx4OAAABAR8O/////wEB/////wAAAAA="; #endregion #endif #endregion @@ -5210,6 +5213,63 @@ public VectorVariableState VectorValue m_vectorValue = value; } } + + /// + public BaseDataVariableState VectorUnionValue + { + get + { + return m_vectorUnionValue; + } + + set + { + if (!Object.ReferenceEquals(m_vectorUnionValue, value)) + { + ChangeMasks |= NodeStateChangeMasks.Children; + } + + m_vectorUnionValue = value; + } + } + + /// + public BaseDataVariableState VectorWithOptionalFieldsValue + { + get + { + return m_vectorWithOptionalFieldsValue; + } + + set + { + if (!Object.ReferenceEquals(m_vectorWithOptionalFieldsValue, value)) + { + ChangeMasks |= NodeStateChangeMasks.Children; + } + + m_vectorWithOptionalFieldsValue = value; + } + } + + /// + public BaseDataVariableState MultipleVectorsValue + { + get + { + return m_multipleVectorsValue; + } + + set + { + if (!Object.ReferenceEquals(m_multipleVectorsValue, value)) + { + ChangeMasks |= NodeStateChangeMasks.Children; + } + + m_multipleVectorsValue = value; + } + } #endregion #region Overridden Methods @@ -5358,6 +5418,21 @@ public override void GetChildren( children.Add(m_vectorValue); } + if (m_vectorUnionValue != null) + { + children.Add(m_vectorUnionValue); + } + + if (m_vectorWithOptionalFieldsValue != null) + { + children.Add(m_vectorWithOptionalFieldsValue); + } + + if (m_multipleVectorsValue != null) + { + children.Add(m_multipleVectorsValue); + } + base.GetChildren(context, children); } @@ -5964,6 +6039,69 @@ protected override BaseInstanceState FindChild( instance = VectorValue; break; } + + case TestData.BrowseNames.VectorUnionValue: + { + if (createOrReplace) + { + if (VectorUnionValue == null) + { + if (replacement == null) + { + VectorUnionValue = new BaseDataVariableState(this); + } + else + { + VectorUnionValue = (BaseDataVariableState)replacement; + } + } + } + + instance = VectorUnionValue; + break; + } + + case TestData.BrowseNames.VectorWithOptionalFieldsValue: + { + if (createOrReplace) + { + if (VectorWithOptionalFieldsValue == null) + { + if (replacement == null) + { + VectorWithOptionalFieldsValue = new BaseDataVariableState(this); + } + else + { + VectorWithOptionalFieldsValue = (BaseDataVariableState)replacement; + } + } + } + + instance = VectorWithOptionalFieldsValue; + break; + } + + case TestData.BrowseNames.MultipleVectorsValue: + { + if (createOrReplace) + { + if (MultipleVectorsValue == null) + { + if (replacement == null) + { + MultipleVectorsValue = new BaseDataVariableState(this); + } + else + { + MultipleVectorsValue = (BaseDataVariableState)replacement; + } + } + } + + instance = MultipleVectorsValue; + break; + } } if (instance != null) @@ -6004,6 +6142,9 @@ protected override BaseInstanceState FindChild( private BaseDataVariableState m_integerValue; private BaseDataVariableState m_uIntegerValue; private VectorVariableState m_vectorValue; + private BaseDataVariableState m_vectorUnionValue; + private BaseDataVariableState m_vectorWithOptionalFieldsValue; + private BaseDataVariableState m_multipleVectorsValue; #endregion } #endif @@ -7543,7 +7684,7 @@ protected override void InitializeOptionalChildren(ISystemContext context) #region Initialization String private const string InitializationString = "AQAAABgAAABodHRwOi8vdGVzdC5vcmcvVUEvRGF0YS//////BGCAAgEAAAABABwAAABBcnJheVZhbHVl" + - "T2JqZWN0VHlwZUluc3RhbmNlAQGwBQEBsAWwBQAAAQAAAAAkAAEBtAUfAAAANWCJCgIAAAABABAAAABT" + + "T2JqZWN0VHlwZUluc3RhbmNlAQGwBQEBsAWwBQAAAQAAAAAkAAEBtAUiAAAANWCJCgIAAAABABAAAABT" + "aW11bGF0aW9uQWN0aXZlAQGxBQMAAAAARwAAAElmIHRydWUgdGhlIHNlcnZlciB3aWxsIHByb2R1Y2Ug" + "bmV3IHZhbHVlcyBmb3IgZWFjaCBtb25pdG9yZWQgdmFyaWFibGUuAC4ARLEFAAAAAf////8BAf////8A" + "AAAABGGCCgQAAAABAA4AAABHZW5lcmF0ZVZhbHVlcwEBsgUALwEB+QOyBQAAAQH/////AQAAABdgqQoC" + @@ -7612,7 +7753,10 @@ protected override void InitializeOptionalChildren(ISystemContext context) "//8AAAAAF2CJCgIAAAABAAwAAABJbnRlZ2VyVmFsdWUBAQgGAC8APwgGAAAAGwEAAAABAAAAAAAAAAEB" + "/////wAAAAAXYIkKAgAAAAEADQAAAFVJbnRlZ2VyVmFsdWUBAQkGAC8APwkGAAAAHAEAAAABAAAAAAAA" + "AAEB/////wAAAAAXYIkKAgAAAAEACwAAAFZlY3RvclZhbHVlAQEKBgAvAD8KBgAAAQFgBwEAAAABAAAA" + - "AAAAAAEB/////wAAAAA="; + "AAAAAAEB/////wAAAAAXYIkKAgAAAAEAEAAAAFZlY3RvclVuaW9uVmFsdWUBARgOAC8APxgOAAABAQAO" + + "AQAAAAEAAAAAAAAAAQH/////AAAAABdgiQoCAAAAAQAdAAAAVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRz" + + "VmFsdWUBARkOAC8APxkOAAABAQEOAQAAAAEAAAAAAAAAAQH/////AAAAABdgiQoCAAAAAQAUAAAATXVs" + + "dGlwbGVWZWN0b3JzVmFsdWUBASsOAC8APysOAAABAR8OAQAAAAEAAAAAAAAAAQH/////AAAAAA=="; #endregion #endif #endregion @@ -8149,6 +8293,63 @@ public BaseDataVariableState VectorValue m_vectorValue = value; } } + + /// + public BaseDataVariableState VectorUnionValue + { + get + { + return m_vectorUnionValue; + } + + set + { + if (!Object.ReferenceEquals(m_vectorUnionValue, value)) + { + ChangeMasks |= NodeStateChangeMasks.Children; + } + + m_vectorUnionValue = value; + } + } + + /// + public BaseDataVariableState VectorWithOptionalFieldsValue + { + get + { + return m_vectorWithOptionalFieldsValue; + } + + set + { + if (!Object.ReferenceEquals(m_vectorWithOptionalFieldsValue, value)) + { + ChangeMasks |= NodeStateChangeMasks.Children; + } + + m_vectorWithOptionalFieldsValue = value; + } + } + + /// + public BaseDataVariableState MultipleVectorsValue + { + get + { + return m_multipleVectorsValue; + } + + set + { + if (!Object.ReferenceEquals(m_multipleVectorsValue, value)) + { + ChangeMasks |= NodeStateChangeMasks.Children; + } + + m_multipleVectorsValue = value; + } + } #endregion #region Overridden Methods @@ -8297,6 +8498,21 @@ public override void GetChildren( children.Add(m_vectorValue); } + if (m_vectorUnionValue != null) + { + children.Add(m_vectorUnionValue); + } + + if (m_vectorWithOptionalFieldsValue != null) + { + children.Add(m_vectorWithOptionalFieldsValue); + } + + if (m_multipleVectorsValue != null) + { + children.Add(m_multipleVectorsValue); + } + base.GetChildren(context, children); } @@ -8903,6 +9119,69 @@ protected override BaseInstanceState FindChild( instance = VectorValue; break; } + + case TestData.BrowseNames.VectorUnionValue: + { + if (createOrReplace) + { + if (VectorUnionValue == null) + { + if (replacement == null) + { + VectorUnionValue = new BaseDataVariableState(this); + } + else + { + VectorUnionValue = (BaseDataVariableState)replacement; + } + } + } + + instance = VectorUnionValue; + break; + } + + case TestData.BrowseNames.VectorWithOptionalFieldsValue: + { + if (createOrReplace) + { + if (VectorWithOptionalFieldsValue == null) + { + if (replacement == null) + { + VectorWithOptionalFieldsValue = new BaseDataVariableState(this); + } + else + { + VectorWithOptionalFieldsValue = (BaseDataVariableState)replacement; + } + } + } + + instance = VectorWithOptionalFieldsValue; + break; + } + + case TestData.BrowseNames.MultipleVectorsValue: + { + if (createOrReplace) + { + if (MultipleVectorsValue == null) + { + if (replacement == null) + { + MultipleVectorsValue = new BaseDataVariableState(this); + } + else + { + MultipleVectorsValue = (BaseDataVariableState)replacement; + } + } + } + + instance = MultipleVectorsValue; + break; + } } if (instance != null) @@ -8943,6 +9222,9 @@ protected override BaseInstanceState FindChild( private BaseDataVariableState m_integerValue; private BaseDataVariableState m_uIntegerValue; private BaseDataVariableState m_vectorValue; + private BaseDataVariableState m_vectorUnionValue; + private BaseDataVariableState m_vectorWithOptionalFieldsValue; + private BaseDataVariableState m_multipleVectorsValue; #endregion } #endif diff --git a/Applications/Quickstarts.Servers/TestData/TestData.Constants.cs b/Applications/Quickstarts.Servers/TestData/TestData.Constants.cs index 5ba3c9047..e65251d33 100644 --- a/Applications/Quickstarts.Servers/TestData/TestData.Constants.cs +++ b/Applications/Quickstarts.Servers/TestData/TestData.Constants.cs @@ -124,6 +124,15 @@ public static partial class DataTypes /// public const uint Vector = 1888; + /// + public const uint VectorUnion = 3584; + + /// + public const uint VectorWithOptionalFields = 3585; + + /// + public const uint MultipleVectors = 3615; + /// public const uint WorkOrderStatusType = 1893; @@ -644,6 +653,15 @@ public static partial class Objects /// public const uint Vector_Encoding_DefaultBinary = 3515; + /// + public const uint VectorUnion_Encoding_DefaultBinary = 3590; + + /// + public const uint VectorWithOptionalFields_Encoding_DefaultBinary = 3591; + + /// + public const uint MultipleVectors_Encoding_DefaultBinary = 3618; + /// public const uint WorkOrderStatusType_Encoding_DefaultBinary = 3516; @@ -665,6 +683,15 @@ public static partial class Objects /// public const uint Vector_Encoding_DefaultXml = 3547; + /// + public const uint VectorUnion_Encoding_DefaultXml = 3598; + + /// + public const uint VectorWithOptionalFields_Encoding_DefaultXml = 3599; + + /// + public const uint MultipleVectors_Encoding_DefaultXml = 3622; + /// public const uint WorkOrderStatusType_Encoding_DefaultXml = 3548; @@ -686,6 +713,15 @@ public static partial class Objects /// public const uint Vector_Encoding_DefaultJson = 3579; + /// + public const uint VectorUnion_Encoding_DefaultJson = 3606; + + /// + public const uint VectorWithOptionalFields_Encoding_DefaultJson = 3607; + + /// + public const uint MultipleVectors_Encoding_DefaultJson = 3626; + /// public const uint WorkOrderStatusType_Encoding_DefaultJson = 3580; @@ -1098,6 +1134,15 @@ public static partial class Variables /// public const uint ScalarValueObjectType_VectorValue_Z = 1209; + /// + public const uint ScalarValueObjectType_VectorUnionValue = 3582; + + /// + public const uint ScalarValueObjectType_VectorWithOptionalFieldsValue = 3583; + + /// + public const uint ScalarValueObjectType_MultipleVectorsValue = 3614; + /// public const uint StructureValueObjectType_GenerateValues_InputArguments = 1213; @@ -1617,6 +1662,15 @@ public static partial class Variables /// public const uint ArrayValueObjectType_VectorValue = 1546; + /// + public const uint ArrayValueObjectType_VectorUnionValue = 3608; + + /// + public const uint ArrayValueObjectType_VectorWithOptionalFieldsValue = 3609; + + /// + public const uint ArrayValueObjectType_MultipleVectorsValue = 3627; + /// public const uint AnalogArrayValueObjectType_GenerateValues_InputArguments = 1550; @@ -2364,6 +2418,15 @@ public static partial class Variables /// public const uint Data_Static_Scalar_VectorValue_Z = 2069; + /// + public const uint Data_Static_Scalar_VectorUnionValue = 3586; + + /// + public const uint Data_Static_Scalar_VectorWithOptionalFieldsValue = 3587; + + /// + public const uint Data_Static_Scalar_MultipleVectorsValue = 3616; + /// public const uint Data_Static_Structure_SimulationActive = 2071; @@ -2724,6 +2787,15 @@ public static partial class Variables /// public const uint Data_Static_Array_VectorValue = 2255; + /// + public const uint Data_Static_Array_VectorUnionValue = 3610; + + /// + public const uint Data_Static_Array_VectorWithOptionalFieldsValue = 3611; + + /// + public const uint Data_Static_Array_MultipleVectorsValue = 3628; + /// public const uint Data_Static_UserScalar_SimulationActive = 2257; @@ -3615,6 +3687,15 @@ public static partial class Variables /// public const uint Data_Dynamic_Scalar_VectorValue_Z = 2833; + /// + public const uint Data_Dynamic_Scalar_VectorUnionValue = 3588; + + /// + public const uint Data_Dynamic_Scalar_VectorWithOptionalFieldsValue = 3589; + + /// + public const uint Data_Dynamic_Scalar_MultipleVectorsValue = 3617; + /// public const uint Data_Dynamic_Structure_SimulationActive = 2835; @@ -3975,6 +4056,15 @@ public static partial class Variables /// public const uint Data_Dynamic_Array_VectorValue = 3019; + /// + public const uint Data_Dynamic_Array_VectorUnionValue = 3612; + + /// + public const uint Data_Dynamic_Array_VectorWithOptionalFieldsValue = 3613; + + /// + public const uint Data_Dynamic_Array_MultipleVectorsValue = 3629; + /// public const uint Data_Dynamic_UserScalar_SimulationActive = 3021; @@ -4719,6 +4809,15 @@ public static partial class Variables /// public const uint TestData_BinarySchema_Vector = 3534; + /// + public const uint TestData_BinarySchema_VectorUnion = 3592; + + /// + public const uint TestData_BinarySchema_VectorWithOptionalFields = 3595; + + /// + public const uint TestData_BinarySchema_MultipleVectors = 3619; + /// public const uint TestData_BinarySchema_WorkOrderStatusType = 3537; @@ -4749,6 +4848,15 @@ public static partial class Variables /// public const uint TestData_XmlSchema_Vector = 3566; + /// + public const uint TestData_XmlSchema_VectorUnion = 3600; + + /// + public const uint TestData_XmlSchema_VectorWithOptionalFields = 3603; + + /// + public const uint TestData_XmlSchema_MultipleVectors = 3623; + /// public const uint TestData_XmlSchema_WorkOrderStatusType = 3569; @@ -4858,6 +4966,15 @@ public static partial class DataTypeIds /// public static readonly ExpandedNodeId Vector = new ExpandedNodeId(TestData.DataTypes.Vector, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId VectorUnion = new ExpandedNodeId(TestData.DataTypes.VectorUnion, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId VectorWithOptionalFields = new ExpandedNodeId(TestData.DataTypes.VectorWithOptionalFields, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId MultipleVectors = new ExpandedNodeId(TestData.DataTypes.MultipleVectors, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId WorkOrderStatusType = new ExpandedNodeId(TestData.DataTypes.WorkOrderStatusType, TestData.Namespaces.TestData); @@ -5378,6 +5495,15 @@ public static partial class ObjectIds /// public static readonly ExpandedNodeId Vector_Encoding_DefaultBinary = new ExpandedNodeId(TestData.Objects.Vector_Encoding_DefaultBinary, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId VectorUnion_Encoding_DefaultBinary = new ExpandedNodeId(TestData.Objects.VectorUnion_Encoding_DefaultBinary, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId VectorWithOptionalFields_Encoding_DefaultBinary = new ExpandedNodeId(TestData.Objects.VectorWithOptionalFields_Encoding_DefaultBinary, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId MultipleVectors_Encoding_DefaultBinary = new ExpandedNodeId(TestData.Objects.MultipleVectors_Encoding_DefaultBinary, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId WorkOrderStatusType_Encoding_DefaultBinary = new ExpandedNodeId(TestData.Objects.WorkOrderStatusType_Encoding_DefaultBinary, TestData.Namespaces.TestData); @@ -5399,6 +5525,15 @@ public static partial class ObjectIds /// public static readonly ExpandedNodeId Vector_Encoding_DefaultXml = new ExpandedNodeId(TestData.Objects.Vector_Encoding_DefaultXml, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId VectorUnion_Encoding_DefaultXml = new ExpandedNodeId(TestData.Objects.VectorUnion_Encoding_DefaultXml, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId VectorWithOptionalFields_Encoding_DefaultXml = new ExpandedNodeId(TestData.Objects.VectorWithOptionalFields_Encoding_DefaultXml, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId MultipleVectors_Encoding_DefaultXml = new ExpandedNodeId(TestData.Objects.MultipleVectors_Encoding_DefaultXml, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId WorkOrderStatusType_Encoding_DefaultXml = new ExpandedNodeId(TestData.Objects.WorkOrderStatusType_Encoding_DefaultXml, TestData.Namespaces.TestData); @@ -5420,6 +5555,15 @@ public static partial class ObjectIds /// public static readonly ExpandedNodeId Vector_Encoding_DefaultJson = new ExpandedNodeId(TestData.Objects.Vector_Encoding_DefaultJson, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId VectorUnion_Encoding_DefaultJson = new ExpandedNodeId(TestData.Objects.VectorUnion_Encoding_DefaultJson, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId VectorWithOptionalFields_Encoding_DefaultJson = new ExpandedNodeId(TestData.Objects.VectorWithOptionalFields_Encoding_DefaultJson, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId MultipleVectors_Encoding_DefaultJson = new ExpandedNodeId(TestData.Objects.MultipleVectors_Encoding_DefaultJson, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId WorkOrderStatusType_Encoding_DefaultJson = new ExpandedNodeId(TestData.Objects.WorkOrderStatusType_Encoding_DefaultJson, TestData.Namespaces.TestData); @@ -5832,6 +5976,15 @@ public static partial class VariableIds /// public static readonly ExpandedNodeId ScalarValueObjectType_VectorValue_Z = new ExpandedNodeId(TestData.Variables.ScalarValueObjectType_VectorValue_Z, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId ScalarValueObjectType_VectorUnionValue = new ExpandedNodeId(TestData.Variables.ScalarValueObjectType_VectorUnionValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId ScalarValueObjectType_VectorWithOptionalFieldsValue = new ExpandedNodeId(TestData.Variables.ScalarValueObjectType_VectorWithOptionalFieldsValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId ScalarValueObjectType_MultipleVectorsValue = new ExpandedNodeId(TestData.Variables.ScalarValueObjectType_MultipleVectorsValue, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId StructureValueObjectType_GenerateValues_InputArguments = new ExpandedNodeId(TestData.Variables.StructureValueObjectType_GenerateValues_InputArguments, TestData.Namespaces.TestData); @@ -6351,6 +6504,15 @@ public static partial class VariableIds /// public static readonly ExpandedNodeId ArrayValueObjectType_VectorValue = new ExpandedNodeId(TestData.Variables.ArrayValueObjectType_VectorValue, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId ArrayValueObjectType_VectorUnionValue = new ExpandedNodeId(TestData.Variables.ArrayValueObjectType_VectorUnionValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId ArrayValueObjectType_VectorWithOptionalFieldsValue = new ExpandedNodeId(TestData.Variables.ArrayValueObjectType_VectorWithOptionalFieldsValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId ArrayValueObjectType_MultipleVectorsValue = new ExpandedNodeId(TestData.Variables.ArrayValueObjectType_MultipleVectorsValue, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId AnalogArrayValueObjectType_GenerateValues_InputArguments = new ExpandedNodeId(TestData.Variables.AnalogArrayValueObjectType_GenerateValues_InputArguments, TestData.Namespaces.TestData); @@ -7098,6 +7260,15 @@ public static partial class VariableIds /// public static readonly ExpandedNodeId Data_Static_Scalar_VectorValue_Z = new ExpandedNodeId(TestData.Variables.Data_Static_Scalar_VectorValue_Z, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId Data_Static_Scalar_VectorUnionValue = new ExpandedNodeId(TestData.Variables.Data_Static_Scalar_VectorUnionValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId Data_Static_Scalar_VectorWithOptionalFieldsValue = new ExpandedNodeId(TestData.Variables.Data_Static_Scalar_VectorWithOptionalFieldsValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId Data_Static_Scalar_MultipleVectorsValue = new ExpandedNodeId(TestData.Variables.Data_Static_Scalar_MultipleVectorsValue, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId Data_Static_Structure_SimulationActive = new ExpandedNodeId(TestData.Variables.Data_Static_Structure_SimulationActive, TestData.Namespaces.TestData); @@ -7458,6 +7629,15 @@ public static partial class VariableIds /// public static readonly ExpandedNodeId Data_Static_Array_VectorValue = new ExpandedNodeId(TestData.Variables.Data_Static_Array_VectorValue, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId Data_Static_Array_VectorUnionValue = new ExpandedNodeId(TestData.Variables.Data_Static_Array_VectorUnionValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId Data_Static_Array_VectorWithOptionalFieldsValue = new ExpandedNodeId(TestData.Variables.Data_Static_Array_VectorWithOptionalFieldsValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId Data_Static_Array_MultipleVectorsValue = new ExpandedNodeId(TestData.Variables.Data_Static_Array_MultipleVectorsValue, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId Data_Static_UserScalar_SimulationActive = new ExpandedNodeId(TestData.Variables.Data_Static_UserScalar_SimulationActive, TestData.Namespaces.TestData); @@ -8349,6 +8529,15 @@ public static partial class VariableIds /// public static readonly ExpandedNodeId Data_Dynamic_Scalar_VectorValue_Z = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Scalar_VectorValue_Z, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId Data_Dynamic_Scalar_VectorUnionValue = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Scalar_VectorUnionValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId Data_Dynamic_Scalar_VectorWithOptionalFieldsValue = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Scalar_VectorWithOptionalFieldsValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId Data_Dynamic_Scalar_MultipleVectorsValue = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Scalar_MultipleVectorsValue, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId Data_Dynamic_Structure_SimulationActive = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Structure_SimulationActive, TestData.Namespaces.TestData); @@ -8709,6 +8898,15 @@ public static partial class VariableIds /// public static readonly ExpandedNodeId Data_Dynamic_Array_VectorValue = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Array_VectorValue, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId Data_Dynamic_Array_VectorUnionValue = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Array_VectorUnionValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId Data_Dynamic_Array_VectorWithOptionalFieldsValue = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Array_VectorWithOptionalFieldsValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId Data_Dynamic_Array_MultipleVectorsValue = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Array_MultipleVectorsValue, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId Data_Dynamic_UserScalar_SimulationActive = new ExpandedNodeId(TestData.Variables.Data_Dynamic_UserScalar_SimulationActive, TestData.Namespaces.TestData); @@ -9453,6 +9651,15 @@ public static partial class VariableIds /// public static readonly ExpandedNodeId TestData_BinarySchema_Vector = new ExpandedNodeId(TestData.Variables.TestData_BinarySchema_Vector, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId TestData_BinarySchema_VectorUnion = new ExpandedNodeId(TestData.Variables.TestData_BinarySchema_VectorUnion, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId TestData_BinarySchema_VectorWithOptionalFields = new ExpandedNodeId(TestData.Variables.TestData_BinarySchema_VectorWithOptionalFields, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId TestData_BinarySchema_MultipleVectors = new ExpandedNodeId(TestData.Variables.TestData_BinarySchema_MultipleVectors, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId TestData_BinarySchema_WorkOrderStatusType = new ExpandedNodeId(TestData.Variables.TestData_BinarySchema_WorkOrderStatusType, TestData.Namespaces.TestData); @@ -9483,6 +9690,15 @@ public static partial class VariableIds /// public static readonly ExpandedNodeId TestData_XmlSchema_Vector = new ExpandedNodeId(TestData.Variables.TestData_XmlSchema_Vector, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId TestData_XmlSchema_VectorUnion = new ExpandedNodeId(TestData.Variables.TestData_XmlSchema_VectorUnion, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId TestData_XmlSchema_VectorWithOptionalFields = new ExpandedNodeId(TestData.Variables.TestData_XmlSchema_VectorWithOptionalFields, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId TestData_XmlSchema_MultipleVectors = new ExpandedNodeId(TestData.Variables.TestData_XmlSchema_MultipleVectors, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId TestData_XmlSchema_WorkOrderStatusType = new ExpandedNodeId(TestData.Variables.TestData_XmlSchema_WorkOrderStatusType, TestData.Namespaces.TestData); @@ -9636,6 +9852,12 @@ public static partial class BrowseNames /// public const string MonitoredNodeCount = "MonitoredNodeCount"; + /// + public const string MultipleVectors = "MultipleVectors"; + + /// + public const string MultipleVectorsValue = "MultipleVectorsValue"; + /// public const string NewValueCount = "NewValueCount"; @@ -9774,12 +9996,24 @@ public static partial class BrowseNames /// public const string VectorStructure = "VectorStructure"; + /// + public const string VectorUnion = "VectorUnion"; + + /// + public const string VectorUnionValue = "VectorUnionValue"; + /// public const string VectorValue = "VectorValue"; /// public const string VectorVariableType = "VectorVariableType"; + /// + public const string VectorWithOptionalFields = "VectorWithOptionalFields"; + + /// + public const string VectorWithOptionalFieldsValue = "VectorWithOptionalFieldsValue"; + /// public const string WorkOrderStatusType = "WorkOrderStatusType"; diff --git a/Applications/Quickstarts.Servers/TestData/TestData.DataTypes.cs b/Applications/Quickstarts.Servers/TestData/TestData.DataTypes.cs index f842cf32f..add5c653a 100644 --- a/Applications/Quickstarts.Servers/TestData/TestData.DataTypes.cs +++ b/Applications/Quickstarts.Servers/TestData/TestData.DataTypes.cs @@ -2863,6 +2863,809 @@ public object Clone() #endif #endregion + #region VectorUnion Class + #if (!OPCUA_EXCLUDE_VectorUnion) + /// + /// + public enum VectorUnionFields : uint + { + /// + None = 0, + /// + X = 1, + /// + Y = 2, + /// + Z = 3 + } + + /// + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Opc.Ua.ModelCompiler", "1.0.0.0")] + [DataContract(Namespace = TestData.Namespaces.TestData)] + public partial class VectorUnion : IEncodeable, IJsonEncodeable + { + #region Constructors + /// + public VectorUnion() + { + Initialize(); + } + + [OnDeserializing] + private void Initialize(StreamingContext context) + { + Initialize(); + } + + private void Initialize() + { + SwitchField = VectorUnionFields.None; + m_x = (double)0; + m_y = (double)0; + m_z = (double)0; + } + #endregion + + #region Public Properties + // + [DataMember(Name = "SwitchField", IsRequired = true, Order = 0)] + public VectorUnionFields SwitchField { get; set; } + + /// + [DataMember(Name = "X", IsRequired = false, Order = 1)] + public double X + { + get { return m_x; } + set { m_x = value; } + } + + /// + [DataMember(Name = "Y", IsRequired = false, Order = 2)] + public double Y + { + get { return m_y; } + set { m_y = value; } + } + + /// + [DataMember(Name = "Z", IsRequired = false, Order = 3)] + public double Z + { + get { return m_z; } + set { m_z = value; } + } + #endregion + + #region IEncodeable Members + /// + public virtual ExpandedNodeId TypeId => DataTypeIds.VectorUnion; + + /// + public virtual ExpandedNodeId BinaryEncodingId => ObjectIds.VectorUnion_Encoding_DefaultBinary; + + /// + public virtual ExpandedNodeId XmlEncodingId => ObjectIds.VectorUnion_Encoding_DefaultXml; + + /// + public virtual ExpandedNodeId JsonEncodingId => ObjectIds.VectorUnion_Encoding_DefaultJson; + + /// + public virtual void Encode(IEncoder encoder) + { + encoder.PushNamespace(TestData.Namespaces.TestData); + encoder.WriteUInt32(nameof(SwitchField), (uint)SwitchField); + + switch (SwitchField) + { + default: { break; } + case VectorUnionFields.X: { encoder.WriteDouble("X", X); break; } + case VectorUnionFields.Y: { encoder.WriteDouble("Y", Y); break; } + case VectorUnionFields.Z: { encoder.WriteDouble("Z", Z); break; } + } + + encoder.PopNamespace(); + } + + /// + public virtual void Decode(IDecoder decoder) + { + decoder.PushNamespace(TestData.Namespaces.TestData); + + SwitchField = (VectorUnionFields)decoder.ReadUInt32(nameof(SwitchField)); + + switch (SwitchField) + { + default: { break; } + case VectorUnionFields.X: { X = decoder.ReadDouble("X"); break; } + case VectorUnionFields.Y: { Y = decoder.ReadDouble("Y"); break; } + case VectorUnionFields.Z: { Z = decoder.ReadDouble("Z"); break; } + } + + decoder.PopNamespace(); + } + + /// + public virtual bool IsEqual(IEncodeable encodeable) + { + if (Object.ReferenceEquals(this, encodeable)) + { + return true; + } + + VectorUnion value = encodeable as VectorUnion; + + if (value == null) + { + return false; + } + + if (value.SwitchField != this.SwitchField) return false; + + switch (SwitchField) + { + default: { break; } + case VectorUnionFields.X: { if (!Utils.IsEqual(m_x, value.m_x)) return false; break; } + case VectorUnionFields.Y: { if (!Utils.IsEqual(m_y, value.m_y)) return false; break; } + case VectorUnionFields.Z: { if (!Utils.IsEqual(m_z, value.m_z)) return false; break; } + } + + return true; + } + + /// + public virtual object Clone() + { + return (VectorUnion)this.MemberwiseClone(); + } + + /// + public new object MemberwiseClone() + { + VectorUnion clone = (VectorUnion)base.MemberwiseClone(); + + clone.SwitchField = this.SwitchField; + + switch (SwitchField) + { + default: { break; } + case VectorUnionFields.X: { clone.m_x = (double)Utils.Clone(this.m_x); break; } + case VectorUnionFields.Y: { clone.m_y = (double)Utils.Clone(this.m_y); break; } + case VectorUnionFields.Z: { clone.m_z = (double)Utils.Clone(this.m_z); break; } + } + + return clone; + } + #endregion + + #region Private Fields + private double m_x; + private double m_y; + private double m_z; + #endregion + } + + #region VectorUnionCollection Class + /// + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Opc.Ua.ModelCompiler", "1.0.0.0")] + [CollectionDataContract(Name = "ListOfVectorUnion", Namespace = TestData.Namespaces.TestData, ItemName = "VectorUnion")] + public partial class VectorUnionCollection : List, ICloneable + { + #region Constructors + /// + public VectorUnionCollection() {} + + /// + public VectorUnionCollection(int capacity) : base(capacity) {} + + /// + public VectorUnionCollection(IEnumerable collection) : base(collection) {} + #endregion + + #region Static Operators + /// + public static implicit operator VectorUnionCollection(VectorUnion[] values) + { + if (values != null) + { + return new VectorUnionCollection(values); + } + + return new VectorUnionCollection(); + } + + /// + public static explicit operator VectorUnion[](VectorUnionCollection values) + { + if (values != null) + { + return values.ToArray(); + } + + return null; + } + #endregion + + #region ICloneable Methods + /// + public object Clone() + { + return (VectorUnionCollection)this.MemberwiseClone(); + } + #endregion + + /// + public new object MemberwiseClone() + { + VectorUnionCollection clone = new VectorUnionCollection(this.Count); + + for (int ii = 0; ii < this.Count; ii++) + { + clone.Add((VectorUnion)Utils.Clone(this[ii])); + } + + return clone; + } + } + #endregion + #endif + #endregion + + #region VectorWithOptionalFields Class + #if (!OPCUA_EXCLUDE_VectorWithOptionalFields) + /// + /// + + public enum VectorWithOptionalFieldsFields : uint + { + None = 0, + /// + X = 0x1, + /// + Y = 0x2, + /// + Z = 0x4 + } + + /// + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Opc.Ua.ModelCompiler", "1.0.0.0")] + [DataContract(Namespace = TestData.Namespaces.TestData)] + public partial class VectorWithOptionalFields : IEncodeable, IJsonEncodeable + { + #region Constructors + /// + public VectorWithOptionalFields() + { + Initialize(); + } + + [OnDeserializing] + private void Initialize(StreamingContext context) + { + Initialize(); + } + + private void Initialize() + { + EncodingMask = VectorWithOptionalFieldsFields.None; + m_x = (double)0; + m_y = (double)0; + m_z = (double)0; + } + #endregion + + #region Public Properties + // + [DataMember(Name = "EncodingMask", IsRequired = true, Order = 0)] + public VectorWithOptionalFieldsFields EncodingMask { get; set; } + + /// + [DataMember(Name = "X", IsRequired = false, Order = 1)] + public double X + { + get { return m_x; } + set { m_x = value; } + } + + /// + [DataMember(Name = "Y", IsRequired = false, Order = 2)] + public double Y + { + get { return m_y; } + set { m_y = value; } + } + + /// + [DataMember(Name = "Z", IsRequired = false, Order = 3)] + public double Z + { + get { return m_z; } + set { m_z = value; } + } + #endregion + + #region IEncodeable Members + /// + public virtual ExpandedNodeId TypeId => DataTypeIds.VectorWithOptionalFields; + + /// + public virtual ExpandedNodeId BinaryEncodingId => ObjectIds.VectorWithOptionalFields_Encoding_DefaultBinary; + + /// + public virtual ExpandedNodeId XmlEncodingId => ObjectIds.VectorWithOptionalFields_Encoding_DefaultXml; + + /// + public virtual ExpandedNodeId JsonEncodingId => ObjectIds.VectorWithOptionalFields_Encoding_DefaultJson; + + /// + public virtual void Encode(IEncoder encoder) + { + encoder.PushNamespace(TestData.Namespaces.TestData); + encoder.WriteUInt32(nameof(EncodingMask), (uint)EncodingMask); + + if ((EncodingMask & VectorWithOptionalFieldsFields.X) != 0) encoder.WriteDouble("X", X); + if ((EncodingMask & VectorWithOptionalFieldsFields.Y) != 0) encoder.WriteDouble("Y", Y); + if ((EncodingMask & VectorWithOptionalFieldsFields.Z) != 0) encoder.WriteDouble("Z", Z); + + encoder.PopNamespace(); + } + + /// + public virtual void Decode(IDecoder decoder) + { + decoder.PushNamespace(TestData.Namespaces.TestData); + + EncodingMask = (VectorWithOptionalFieldsFields)decoder.ReadUInt32(nameof(EncodingMask)); + + if ((EncodingMask & VectorWithOptionalFieldsFields.X) != 0) X = decoder.ReadDouble("X"); + if ((EncodingMask & VectorWithOptionalFieldsFields.Y) != 0) Y = decoder.ReadDouble("Y"); + if ((EncodingMask & VectorWithOptionalFieldsFields.Z) != 0) Z = decoder.ReadDouble("Z"); + + decoder.PopNamespace(); + } + + /// + public virtual bool IsEqual(IEncodeable encodeable) + { + if (Object.ReferenceEquals(this, encodeable)) + { + return true; + } + + VectorWithOptionalFields value = encodeable as VectorWithOptionalFields; + + if (value == null) + { + return false; + } + + if (value.EncodingMask != this.EncodingMask) return false; + + if ((EncodingMask & VectorWithOptionalFieldsFields.X) != 0) if (!Utils.IsEqual(m_x, value.m_x)) return false; + if ((EncodingMask & VectorWithOptionalFieldsFields.Y) != 0) if (!Utils.IsEqual(m_y, value.m_y)) return false; + if ((EncodingMask & VectorWithOptionalFieldsFields.Z) != 0) if (!Utils.IsEqual(m_z, value.m_z)) return false; + + return true; + } + + /// + public virtual object Clone() + { + return (VectorWithOptionalFields)this.MemberwiseClone(); + } + + /// + public new object MemberwiseClone() + { + VectorWithOptionalFields clone = (VectorWithOptionalFields)base.MemberwiseClone(); + + clone.EncodingMask = this.EncodingMask; + + if ((EncodingMask & VectorWithOptionalFieldsFields.X) != 0) clone.m_x = (double)Utils.Clone(this.m_x); + if ((EncodingMask & VectorWithOptionalFieldsFields.Y) != 0) clone.m_y = (double)Utils.Clone(this.m_y); + if ((EncodingMask & VectorWithOptionalFieldsFields.Z) != 0) clone.m_z = (double)Utils.Clone(this.m_z); + + return clone; + } + #endregion + + #region Private Fields + private double m_x; + private double m_y; + private double m_z; + #endregion + } + + #region VectorWithOptionalFieldsCollection Class + /// + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Opc.Ua.ModelCompiler", "1.0.0.0")] + [CollectionDataContract(Name = "ListOfVectorWithOptionalFields", Namespace = TestData.Namespaces.TestData, ItemName = "VectorWithOptionalFields")] + public partial class VectorWithOptionalFieldsCollection : List, ICloneable + { + #region Constructors + /// + public VectorWithOptionalFieldsCollection() {} + + /// + public VectorWithOptionalFieldsCollection(int capacity) : base(capacity) {} + + /// + public VectorWithOptionalFieldsCollection(IEnumerable collection) : base(collection) {} + #endregion + + #region Static Operators + /// + public static implicit operator VectorWithOptionalFieldsCollection(VectorWithOptionalFields[] values) + { + if (values != null) + { + return new VectorWithOptionalFieldsCollection(values); + } + + return new VectorWithOptionalFieldsCollection(); + } + + /// + public static explicit operator VectorWithOptionalFields[](VectorWithOptionalFieldsCollection values) + { + if (values != null) + { + return values.ToArray(); + } + + return null; + } + #endregion + + #region ICloneable Methods + /// + public object Clone() + { + return (VectorWithOptionalFieldsCollection)this.MemberwiseClone(); + } + #endregion + + /// + public new object MemberwiseClone() + { + VectorWithOptionalFieldsCollection clone = new VectorWithOptionalFieldsCollection(this.Count); + + for (int ii = 0; ii < this.Count; ii++) + { + clone.Add((VectorWithOptionalFields)Utils.Clone(this[ii])); + } + + return clone; + } + } + #endregion + #endif + #endregion + + #region MultipleVectors Class + #if (!OPCUA_EXCLUDE_MultipleVectors) + /// + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Opc.Ua.ModelCompiler", "1.0.0.0")] + [DataContract(Namespace = TestData.Namespaces.TestData)] + public partial class MultipleVectors : IEncodeable, IJsonEncodeable + { + #region Constructors + /// + public MultipleVectors() + { + Initialize(); + } + + [OnDeserializing] + private void Initialize(StreamingContext context) + { + Initialize(); + } + + private void Initialize() + { + m_vector = new Vector(); + m_vectorUnion = new VectorUnion(); + m_vectorWithOptionalFields = new VectorWithOptionalFields(); + m_vectorArray = new VectorCollection(); + m_vectorUnionArray = new VectorUnionCollection(); + m_vectorWithOptionalFieldsArray = new VectorWithOptionalFieldsCollection(); + } + #endregion + + #region Public Properties + /// + [DataMember(Name = "Vector", IsRequired = false, Order = 1)] + public Vector Vector + { + get + { + return m_vector; + } + + set + { + m_vector = value; + + if (value == null) + { + m_vector = new Vector(); + } + } + } + + /// + [DataMember(Name = "VectorUnion", IsRequired = false, Order = 2)] + public VectorUnion VectorUnion + { + get + { + return m_vectorUnion; + } + + set + { + m_vectorUnion = value; + + if (value == null) + { + m_vectorUnion = new VectorUnion(); + } + } + } + + /// + [DataMember(Name = "VectorWithOptionalFields", IsRequired = false, Order = 3)] + public VectorWithOptionalFields VectorWithOptionalFields + { + get + { + return m_vectorWithOptionalFields; + } + + set + { + m_vectorWithOptionalFields = value; + + if (value == null) + { + m_vectorWithOptionalFields = new VectorWithOptionalFields(); + } + } + } + + /// + [DataMember(Name = "VectorArray", IsRequired = false, Order = 4)] + public VectorCollection VectorArray + { + get + { + return m_vectorArray; + } + + set + { + m_vectorArray = value; + + if (value == null) + { + m_vectorArray = new VectorCollection(); + } + } + } + + /// + [DataMember(Name = "VectorUnionArray", IsRequired = false, Order = 5)] + public VectorUnionCollection VectorUnionArray + { + get + { + return m_vectorUnionArray; + } + + set + { + m_vectorUnionArray = value; + + if (value == null) + { + m_vectorUnionArray = new VectorUnionCollection(); + } + } + } + + /// + [DataMember(Name = "VectorWithOptionalFieldsArray", IsRequired = false, Order = 6)] + public VectorWithOptionalFieldsCollection VectorWithOptionalFieldsArray + { + get + { + return m_vectorWithOptionalFieldsArray; + } + + set + { + m_vectorWithOptionalFieldsArray = value; + + if (value == null) + { + m_vectorWithOptionalFieldsArray = new VectorWithOptionalFieldsCollection(); + } + } + } + #endregion + + #region IEncodeable Members + /// + public virtual ExpandedNodeId TypeId => DataTypeIds.MultipleVectors; + + /// + public virtual ExpandedNodeId BinaryEncodingId => ObjectIds.MultipleVectors_Encoding_DefaultBinary; + + /// + public virtual ExpandedNodeId XmlEncodingId => ObjectIds.MultipleVectors_Encoding_DefaultXml; + + /// + public virtual ExpandedNodeId JsonEncodingId => ObjectIds.MultipleVectors_Encoding_DefaultJson; + + /// + public virtual void Encode(IEncoder encoder) + { + encoder.PushNamespace(TestData.Namespaces.TestData); + + encoder.WriteEncodeable("Vector", Vector, typeof(Vector)); + encoder.WriteEncodeable("VectorUnion", VectorUnion, typeof(VectorUnion)); + encoder.WriteEncodeable("VectorWithOptionalFields", VectorWithOptionalFields, typeof(VectorWithOptionalFields)); + encoder.WriteEncodeableArray("VectorArray", VectorArray.ToArray(), typeof(Vector)); + encoder.WriteEncodeableArray("VectorUnionArray", VectorUnionArray.ToArray(), typeof(VectorUnion)); + encoder.WriteEncodeableArray("VectorWithOptionalFieldsArray", VectorWithOptionalFieldsArray.ToArray(), typeof(VectorWithOptionalFields)); + + encoder.PopNamespace(); + } + + /// + public virtual void Decode(IDecoder decoder) + { + decoder.PushNamespace(TestData.Namespaces.TestData); + + Vector = (Vector)decoder.ReadEncodeable("Vector", typeof(Vector)); + VectorUnion = (VectorUnion)decoder.ReadEncodeable("VectorUnion", typeof(VectorUnion)); + VectorWithOptionalFields = (VectorWithOptionalFields)decoder.ReadEncodeable("VectorWithOptionalFields", typeof(VectorWithOptionalFields)); + VectorArray = (VectorCollection)decoder.ReadEncodeableArray("VectorArray", typeof(Vector)); + VectorUnionArray = (VectorUnionCollection)decoder.ReadEncodeableArray("VectorUnionArray", typeof(VectorUnion)); + VectorWithOptionalFieldsArray = (VectorWithOptionalFieldsCollection)decoder.ReadEncodeableArray("VectorWithOptionalFieldsArray", typeof(VectorWithOptionalFields)); + + decoder.PopNamespace(); + } + + /// + public virtual bool IsEqual(IEncodeable encodeable) + { + if (Object.ReferenceEquals(this, encodeable)) + { + return true; + } + + MultipleVectors value = encodeable as MultipleVectors; + + if (value == null) + { + return false; + } + + if (!Utils.IsEqual(m_vector, value.m_vector)) return false; + if (!Utils.IsEqual(m_vectorUnion, value.m_vectorUnion)) return false; + if (!Utils.IsEqual(m_vectorWithOptionalFields, value.m_vectorWithOptionalFields)) return false; + if (!Utils.IsEqual(m_vectorArray, value.m_vectorArray)) return false; + if (!Utils.IsEqual(m_vectorUnionArray, value.m_vectorUnionArray)) return false; + if (!Utils.IsEqual(m_vectorWithOptionalFieldsArray, value.m_vectorWithOptionalFieldsArray)) return false; + + return true; + } + + /// + public virtual object Clone() + { + return (MultipleVectors)this.MemberwiseClone(); + } + + /// + public new object MemberwiseClone() + { + MultipleVectors clone = (MultipleVectors)base.MemberwiseClone(); + + clone.m_vector = (Vector)Utils.Clone(this.m_vector); + clone.m_vectorUnion = (VectorUnion)Utils.Clone(this.m_vectorUnion); + clone.m_vectorWithOptionalFields = (VectorWithOptionalFields)Utils.Clone(this.m_vectorWithOptionalFields); + clone.m_vectorArray = (VectorCollection)Utils.Clone(this.m_vectorArray); + clone.m_vectorUnionArray = (VectorUnionCollection)Utils.Clone(this.m_vectorUnionArray); + clone.m_vectorWithOptionalFieldsArray = (VectorWithOptionalFieldsCollection)Utils.Clone(this.m_vectorWithOptionalFieldsArray); + + return clone; + } + #endregion + + #region Private Fields + private Vector m_vector; + private VectorUnion m_vectorUnion; + private VectorWithOptionalFields m_vectorWithOptionalFields; + private VectorCollection m_vectorArray; + private VectorUnionCollection m_vectorUnionArray; + private VectorWithOptionalFieldsCollection m_vectorWithOptionalFieldsArray; + #endregion + } + + #region MultipleVectorsCollection Class + /// + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Opc.Ua.ModelCompiler", "1.0.0.0")] + [CollectionDataContract(Name = "ListOfMultipleVectors", Namespace = TestData.Namespaces.TestData, ItemName = "MultipleVectors")] + public partial class MultipleVectorsCollection : List, ICloneable + { + #region Constructors + /// + public MultipleVectorsCollection() {} + + /// + public MultipleVectorsCollection(int capacity) : base(capacity) {} + + /// + public MultipleVectorsCollection(IEnumerable collection) : base(collection) {} + #endregion + + #region Static Operators + /// + public static implicit operator MultipleVectorsCollection(MultipleVectors[] values) + { + if (values != null) + { + return new MultipleVectorsCollection(values); + } + + return new MultipleVectorsCollection(); + } + + /// + public static explicit operator MultipleVectors[](MultipleVectorsCollection values) + { + if (values != null) + { + return values.ToArray(); + } + + return null; + } + #endregion + + #region ICloneable Methods + /// + public object Clone() + { + return (MultipleVectorsCollection)this.MemberwiseClone(); + } + #endregion + + /// + public new object MemberwiseClone() + { + MultipleVectorsCollection clone = new MultipleVectorsCollection(this.Count); + + for (int ii = 0; ii < this.Count; ii++) + { + clone.Add((MultipleVectors)Utils.Clone(this[ii])); + } + + return clone; + } + } + #endregion + #endif + #endregion + #region WorkOrderStatusType Class #if (!OPCUA_EXCLUDE_WorkOrderStatusType) /// diff --git a/Applications/Quickstarts.Servers/TestData/TestData.NodeIds.csv b/Applications/Quickstarts.Servers/TestData/TestData.NodeIds.csv index 0f3adf628..64b174251 100644 --- a/Applications/Quickstarts.Servers/TestData/TestData.NodeIds.csv +++ b/Applications/Quickstarts.Servers/TestData/TestData.NodeIds.csv @@ -1292,3 +1292,39 @@ UserArrayValueDataType_Encoding_DefaultJson,3578,Object Vector_Encoding_DefaultJson,3579,Object WorkOrderStatusType_Encoding_DefaultJson,3580,Object WorkOrderType_Encoding_DefaultJson,3581,Object +ScalarValueObjectType_VectorUnionValue,3582,Variable +ScalarValueObjectType_VectorWithOptionalFieldsValue,3583,Variable +VectorUnion,3584,DataType +VectorWithOptionalFields,3585,DataType +Data_Static_Scalar_VectorUnionValue,3586,Variable +Data_Static_Scalar_VectorWithOptionalFieldsValue,3587,Variable +Data_Dynamic_Scalar_VectorUnionValue,3588,Variable +Data_Dynamic_Scalar_VectorWithOptionalFieldsValue,3589,Variable +VectorUnion_Encoding_DefaultBinary,3590,Object +VectorWithOptionalFields_Encoding_DefaultBinary,3591,Object +TestData_BinarySchema_VectorUnion,3592,Variable +TestData_BinarySchema_VectorWithOptionalFields,3595,Variable +VectorUnion_Encoding_DefaultXml,3598,Object +VectorWithOptionalFields_Encoding_DefaultXml,3599,Object +TestData_XmlSchema_VectorUnion,3600,Variable +TestData_XmlSchema_VectorWithOptionalFields,3603,Variable +VectorUnion_Encoding_DefaultJson,3606,Object +VectorWithOptionalFields_Encoding_DefaultJson,3607,Object +ArrayValueObjectType_VectorUnionValue,3608,Variable +ArrayValueObjectType_VectorWithOptionalFieldsValue,3609,Variable +Data_Static_Array_VectorUnionValue,3610,Variable +Data_Static_Array_VectorWithOptionalFieldsValue,3611,Variable +Data_Dynamic_Array_VectorUnionValue,3612,Variable +Data_Dynamic_Array_VectorWithOptionalFieldsValue,3613,Variable +ScalarValueObjectType_MultipleVectorsValue,3614,Variable +MultipleVectors,3615,DataType +Data_Static_Scalar_MultipleVectorsValue,3616,Variable +Data_Dynamic_Scalar_MultipleVectorsValue,3617,Variable +MultipleVectors_Encoding_DefaultBinary,3618,Object +TestData_BinarySchema_MultipleVectors,3619,Variable +MultipleVectors_Encoding_DefaultXml,3622,Object +TestData_XmlSchema_MultipleVectors,3623,Variable +MultipleVectors_Encoding_DefaultJson,3626,Object +ArrayValueObjectType_MultipleVectorsValue,3627,Variable +Data_Static_Array_MultipleVectorsValue,3628,Variable +Data_Dynamic_Array_MultipleVectorsValue,3629,Variable diff --git a/Applications/Quickstarts.Servers/TestData/TestData.NodeSet.xml b/Applications/Quickstarts.Servers/TestData/TestData.NodeSet.xml index fae32aed9..ffaadd61a 100644 --- a/Applications/Quickstarts.Servers/TestData/TestData.NodeSet.xml +++ b/Applications/Quickstarts.Servers/TestData/TestData.NodeSet.xml @@ -5167,6 +5167,33 @@ ns=1;i=1206 + + + i=47 + + false + + ns=1;i=3582 + + + + + i=47 + + false + + ns=1;i=3583 + + + + + i=47 + + false + + ns=1;i=3614 + + false @@ -11840,6 +11867,33 @@ ns=1;i=1546 + + + i=47 + + false + + ns=1;i=3608 + + + + + i=47 + + false + + ns=1;i=3609 + + + + + i=47 + + false + + ns=1;i=3627 + + false @@ -25935,6 +25989,33 @@ ns=1;i=2066 + + + i=47 + + false + + ns=1;i=3586 + + + + + i=47 + + false + + ns=1;i=3587 + + + + + i=47 + + false + + ns=1;i=3616 + + 0 @@ -34568,6 +34649,33 @@ ns=1;i=2255 + + + i=47 + + false + + ns=1;i=3610 + + + + + i=47 + + false + + ns=1;i=3611 + + + + + i=47 + + false + + ns=1;i=3628 + + 0 @@ -58945,6 +59053,33 @@ ns=1;i=2830 + + + i=47 + + false + + ns=1;i=3588 + + + + + i=47 + + false + + ns=1;i=3589 + + + + + i=47 + + false + + ns=1;i=3617 + + 0 @@ -67578,6 +67713,33 @@ ns=1;i=3019 + + + i=47 + + false + + ns=1;i=3612 + + + + + i=47 + + false + + ns=1;i=3613 + + + + + i=47 + + false + + ns=1;i=3629 + + 0 @@ -89042,6 +89204,33 @@ ns=1;i=3534 + + + i=47 + + false + + ns=1;i=3592 + + + + + i=47 + + false + + ns=1;i=3595 + + + + + i=47 + + false + + ns=1;i=3619 + + i=47 @@ -89302,20 +89491,44 @@ IE5hbWU9IlZlY3RvciIgQmFzZVR5cGU9InVhOkV4dGVuc2lvbk9iamVjdCI+DQogICAgPG9wYzpG aWVsZCBOYW1lPSJYIiBUeXBlTmFtZT0ib3BjOkRvdWJsZSIgLz4NCiAgICA8b3BjOkZpZWxkIE5h bWU9IlkiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iWiIg VHlwZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQogIDxv -cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgQmFzZVR5cGU9InVh -OkV4dGVuc2lvbk9iamVjdCI+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJBY3RvciIgVHlwZU5hbWU9 -Im9wYzpTdHJpbmciIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJUaW1lc3RhbXAiIFR5cGVOYW1l -PSJvcGM6RGF0ZVRpbWUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJDb21tZW50IiBUeXBlTmFt -ZT0idWE6TG9jYWxpemVkVGV4dCIgLz4NCiAgPC9vcGM6U3RydWN0dXJlZFR5cGU+DQoNCiAgPG9w -YzpTdHJ1Y3R1cmVkVHlwZSBOYW1lPSJXb3JrT3JkZXJUeXBlIiBCYXNlVHlwZT0idWE6RXh0ZW5z -aW9uT2JqZWN0Ij4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IklEIiBUeXBlTmFtZT0ib3BjOkd1aWQi -IC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJBc3NldElEIiBUeXBlTmFtZT0ib3BjOlN0cmluZyIg -Lz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlN0YXJ0VGltZSIgVHlwZU5hbWU9Im9wYzpEYXRlVGlt -ZSIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9Ik5vT2ZTdGF0dXNDb21tZW50cyIgVHlwZU5hbWU9 -Im9wYzpJbnQzMiIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlN0YXR1c0NvbW1lbnRzIiBUeXBl -TmFtZT0idG5zOldvcmtPcmRlclN0YXR1c1R5cGUiIExlbmd0aEZpZWxkPSJOb09mU3RhdHVzQ29t -bWVudHMiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQo8L29wYzpUeXBlRGljdGlvbmFy -eT4= +cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iVmVjdG9yVW5pb24iIEJhc2VUeXBlPSJ1YTpVbmlvbiI+ +DQogICAgPG9wYzpGaWVsZCBOYW1lPSJYIiBUeXBlTmFtZT0ib3BjOkRvdWJsZSIgLz4NCiAgICA8 +b3BjOkZpZWxkIE5hbWU9IlkiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6Rmll +bGQgTmFtZT0iWiIgVHlwZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRU +eXBlPg0KDQogIDxvcGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmll +bGRzIiBCYXNlVHlwZT0idWE6RXh0ZW5zaW9uT2JqZWN0Ij4NCiAgICA8b3BjOkZpZWxkIE5hbWU9 +IlgiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iWSIgVHlw +ZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJaIiBUeXBlTmFtZT0i +b3BjOkRvdWJsZSIgLz4NCiAgPC9vcGM6U3RydWN0dXJlZFR5cGU+DQoNCiAgPG9wYzpTdHJ1Y3R1 +cmVkVHlwZSBOYW1lPSJNdWx0aXBsZVZlY3RvcnMiIEJhc2VUeXBlPSJ1YTpFeHRlbnNpb25PYmpl +Y3QiPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yIiBUeXBlTmFtZT0idG5zOlZlY3RvciIg +Lz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlZlY3RvclVuaW9uIiBUeXBlTmFtZT0idG5zOlZlY3Rv +clVuaW9uIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRz +IiBUeXBlTmFtZT0idG5zOlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyIgLz4NCiAgICA8b3BjOkZp +ZWxkIE5hbWU9Ik5vT2ZWZWN0b3JBcnJheSIgVHlwZU5hbWU9Im9wYzpJbnQzMiIgLz4NCiAgICA8 +b3BjOkZpZWxkIE5hbWU9IlZlY3RvckFycmF5IiBUeXBlTmFtZT0idG5zOlZlY3RvciIgTGVuZ3Ro +RmllbGQ9Ik5vT2ZWZWN0b3JBcnJheSIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9Ik5vT2ZWZWN0 +b3JVbmlvbkFycmF5IiBUeXBlTmFtZT0ib3BjOkludDMyIiAvPg0KICAgIDxvcGM6RmllbGQgTmFt +ZT0iVmVjdG9yVW5pb25BcnJheSIgVHlwZU5hbWU9InRuczpWZWN0b3JVbmlvbiIgTGVuZ3RoRmll +bGQ9Ik5vT2ZWZWN0b3JVbmlvbkFycmF5IiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iTm9PZlZl +Y3RvcldpdGhPcHRpb25hbEZpZWxkc0FycmF5IiBUeXBlTmFtZT0ib3BjOkludDMyIiAvPg0KICAg +IDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzQXJyYXkiIFR5cGVOYW1l +PSJ0bnM6VmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzIiBMZW5ndGhGaWVsZD0iTm9PZlZlY3Rvcldp +dGhPcHRpb25hbEZpZWxkc0FycmF5IiAvPg0KICA8L29wYzpTdHJ1Y3R1cmVkVHlwZT4NCg0KICA8 +b3BjOlN0cnVjdHVyZWRUeXBlIE5hbWU9IldvcmtPcmRlclN0YXR1c1R5cGUiIEJhc2VUeXBlPSJ1 +YTpFeHRlbnNpb25PYmplY3QiPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQWN0b3IiIFR5cGVOYW1l +PSJvcGM6U3RyaW5nIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVGltZXN0YW1wIiBUeXBlTmFt +ZT0ib3BjOkRhdGVUaW1lIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQ29tbWVudCIgVHlwZU5h +bWU9InVhOkxvY2FsaXplZFRleHQiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQogIDxv +cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iV29ya09yZGVyVHlwZSIgQmFzZVR5cGU9InVhOkV4dGVu +c2lvbk9iamVjdCI+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJJRCIgVHlwZU5hbWU9Im9wYzpHdWlk +IiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQXNzZXRJRCIgVHlwZU5hbWU9Im9wYzpTdHJpbmci +IC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJTdGFydFRpbWUiIFR5cGVOYW1lPSJvcGM6RGF0ZVRp +bWUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJOb09mU3RhdHVzQ29tbWVudHMiIFR5cGVOYW1l +PSJvcGM6SW50MzIiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJTdGF0dXNDb21tZW50cyIgVHlw +ZU5hbWU9InRuczpXb3JrT3JkZXJTdGF0dXNUeXBlIiBMZW5ndGhGaWVsZD0iTm9PZlN0YXR1c0Nv +bW1lbnRzIiAvPg0KICA8L29wYzpTdHJ1Y3R1cmVkVHlwZT4NCg0KPC9vcGM6VHlwZURpY3Rpb25h +cnk+ @@ -90275,6 +90488,33 @@ eT4= ns=1;i=3566 + + + i=47 + + false + + ns=1;i=3600 + + + + + i=47 + + false + + ns=1;i=3603 + + + + + i=47 + + false + + ns=1;i=3623 + + i=47 @@ -90302,7 +90542,7 @@ c2QiDQogIHhtbG5zOnRucz0iaHR0cDovL3Rlc3Qub3JnL1VBL0RhdGEvIg0KICB0YXJnZXROYW1l c3BhY2U9Imh0dHA6Ly90ZXN0Lm9yZy9VQS9EYXRhLyINCiAgZWxlbWVudEZvcm1EZWZhdWx0PSJx dWFsaWZpZWQiDQo+DQogIDx4czphbm5vdGF0aW9uPg0KICAgIDx4czphcHBpbmZvPg0KICAgICAg PHVhOk1vZGVsIE1vZGVsVXJpPSJodHRwOi8vdGVzdC5vcmcvVUEvRGF0YS8iIFZlcnNpb249IjEu -MC4wIiBQdWJsaWNhdGlvbkRhdGU9IjIwMjMtMDYtMDZUMDY6MzM6NTEuNTc2OTYyOVoiIC8+DQog +MC4wIiBQdWJsaWNhdGlvbkRhdGU9IjIwMjMtMTAtMDlUMDg6NTE6MjQuNTIzNzA4OFoiIC8+DQog ICAgPC94czphcHBpbmZvPg0KICA8L3hzOmFubm90YXRpb24+DQogIA0KICA8eHM6aW1wb3J0IG5h bWVzcGFjZT0iaHR0cDovL29wY2ZvdW5kYXRpb24ub3JnL1VBLzIwMDgvMDIvVHlwZXMueHNkIiAv Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJTY2FsYXJTdHJ1Y3R1cmVEYXRhVHlwZSI+DQog @@ -90541,35 +90781,82 @@ YW1lPSJWZWN0b3IiIHR5cGU9InRuczpWZWN0b3IiIG1pbk9jY3Vycz0iMCIgbWF4T2NjdXJzPSJ1 bmJvdW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6 Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZlZlY3RvciIgdHlwZT0idG5z Okxpc3RPZlZlY3RvciIgbmlsbGFibGU9InRydWUiPjwveHM6ZWxlbWVudD4NCg0KICA8eHM6Y29t -cGxleFR5cGUgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0K -ICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQWN0b3IiIHR5cGU9InhzOnN0cmluZyIgbWluT2NjdXJz -PSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlRpbWVzdGFt -cCIgdHlwZT0ieHM6ZGF0ZVRpbWUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4czplbGVtZW50 -IG5hbWU9IkNvbW1lbnQiIHR5cGU9InVhOkxvY2FsaXplZFRleHQiIG1pbk9jY3Vycz0iMCIgbmls -bGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4VHlwZT4N -CiAgPHhzOmVsZW1lbnQgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgdHlwZT0idG5zOldvcmtP -cmRlclN0YXR1c1R5cGUiIC8+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RPZldvcmtP -cmRlclN0YXR1c1R5cGUiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4czplbGVtZW50IG5h -bWU9IldvcmtPcmRlclN0YXR1c1R5cGUiIHR5cGU9InRuczpXb3JrT3JkZXJTdGF0dXNUeXBlIiBt -aW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAg -ICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1l -PSJMaXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiB0eXBlPSJ0bnM6TGlzdE9mV29ya09yZGVyU3Rh -dHVzVHlwZSIgbmlsbGFibGU9InRydWUiPjwveHM6ZWxlbWVudD4NCg0KICA8eHM6Y29tcGxleFR5 -cGUgbmFtZT0iV29ya09yZGVyVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVs -ZW1lbnQgbmFtZT0iSUQiIHR5cGU9InVhOkd1aWQiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4 -czplbGVtZW50IG5hbWU9IkFzc2V0SUQiIHR5cGU9InhzOnN0cmluZyIgbWluT2NjdXJzPSIwIiBu -aWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlN0YXJ0VGltZSIgdHlw -ZT0ieHM6ZGF0ZVRpbWUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9 -IlN0YXR1c0NvbW1lbnRzIiB0eXBlPSJ0bnM6TGlzdE9mV29ya09yZGVyU3RhdHVzVHlwZSIgbWlu -T2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hz -OmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3JkZXJUeXBlIiB0eXBlPSJ0 -bnM6V29ya09yZGVyVHlwZSIgLz4NCg0KICA8eHM6Y29tcGxleFR5cGUgbmFtZT0iTGlzdE9mV29y -a09yZGVyVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0i -V29ya09yZGVyVHlwZSIgdHlwZT0idG5zOldvcmtPcmRlclR5cGUiIG1pbk9jY3Vycz0iMCIgbWF4 -T2NjdXJzPSJ1bmJvdW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+ -DQogIDwveHM6Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZldvcmtPcmRl -clR5cGUiIHR5cGU9InRuczpMaXN0T2ZXb3JrT3JkZXJUeXBlIiBuaWxsYWJsZT0idHJ1ZSI+PC94 -czplbGVtZW50Pg0KDQo8L3hzOnNjaGVtYT4= +cGxleFR5cGUgbmFtZT0iVmVjdG9yVW5pb24iPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4 +czplbGVtZW50IG5hbWU9IlN3aXRjaEZpZWxkIiB0eXBlPSJ4czp1bnNpZ25lZEludCIgbWluT2Nj +dXJzPSIwIiAvPg0KICAgICAgPHhzOmNob2ljZT4NCiAgICAgICAgPHhzOmVsZW1lbnQgbmFtZT0i +WCIgdHlwZT0ieHM6ZG91YmxlIiBtaW5PY2N1cnM9IjAiIC8+DQogICAgICAgIDx4czplbGVtZW50 +IG5hbWU9IlkiIHR5cGU9InhzOmRvdWJsZSIgbWluT2NjdXJzPSIwIiAvPg0KICAgICAgICA8eHM6 +ZWxlbWVudCBuYW1lPSJaIiB0eXBlPSJ4czpkb3VibGUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAg +IDwveHM6Y2hvaWNlPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29tcGxleFR5cGU+DQog +IDx4czplbGVtZW50IG5hbWU9IlZlY3RvclVuaW9uIiB0eXBlPSJ0bnM6VmVjdG9yVW5pb24iIC8+ +DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RPZlZlY3RvclVuaW9uIj4NCiAgICA8eHM6 +c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJWZWN0b3JVbmlvbiIgdHlwZT0idG5z +OlZlY3RvclVuaW9uIiBtaW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBuaWxsYWJs +ZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8 +eHM6ZWxlbWVudCBuYW1lPSJMaXN0T2ZWZWN0b3JVbmlvbiIgdHlwZT0idG5zOkxpc3RPZlZlY3Rv +clVuaW9uIiBuaWxsYWJsZT0idHJ1ZSI+PC94czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlw +ZSBuYW1lPSJWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAg +ICAgIDx4czplbGVtZW50IG5hbWU9IlgiIHR5cGU9InhzOmRvdWJsZSIgbWluT2NjdXJzPSIwIiAv +Pg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iWSIgdHlwZT0ieHM6ZG91YmxlIiBtaW5PY2N1cnM9 +IjAiIC8+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJaIiB0eXBlPSJ4czpkb3VibGUiIG1pbk9j +Y3Vycz0iMCIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8 +eHM6ZWxlbWVudCBuYW1lPSJWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIHR5cGU9InRuczpWZWN0 +b3JXaXRoT3B0aW9uYWxGaWVsZHMiIC8+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RP +ZlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzIiB0eXBlPSJ0bnM6VmVjdG9y +V2l0aE9wdGlvbmFsRmllbGRzIiBtaW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBu +aWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBl +Pg0KICA8eHM6ZWxlbWVudCBuYW1lPSJMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIHR5 +cGU9InRuczpMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIG5pbGxhYmxlPSJ0cnVlIj48 +L3hzOmVsZW1lbnQ+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ik11bHRpcGxlVmVjdG9ycyI+ +DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yIiB0eXBl +PSJ0bnM6VmVjdG9yIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iVmVjdG9yVW5pb24iIHR5cGU9InRuczpWZWN0b3JVbmlvbiIgbWluT2Nj +dXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlZlY3Rv +cldpdGhPcHRpb25hbEZpZWxkcyIgdHlwZT0idG5zOlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyIg +bWluT2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9 +IlZlY3RvckFycmF5IiB0eXBlPSJ0bnM6TGlzdE9mVmVjdG9yIiBtaW5PY2N1cnM9IjAiIG5pbGxh +YmxlPSJ0cnVlIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yVW5pb25BcnJheSIg +dHlwZT0idG5zOkxpc3RPZlZlY3RvclVuaW9uIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVl +IiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzQXJy +YXkiIHR5cGU9InRuczpMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIG1pbk9jY3Vycz0i +MCIgbmlsbGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4 +VHlwZT4NCiAgPHhzOmVsZW1lbnQgbmFtZT0iTXVsdGlwbGVWZWN0b3JzIiB0eXBlPSJ0bnM6TXVs +dGlwbGVWZWN0b3JzIiAvPg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJMaXN0T2ZNdWx0aXBs +ZVZlY3RvcnMiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9Ik11 +bHRpcGxlVmVjdG9ycyIgdHlwZT0idG5zOk11bHRpcGxlVmVjdG9ycyIgbWluT2NjdXJzPSIwIiBt +YXhPY2N1cnM9InVuYm91bmRlZCIgbmlsbGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5j +ZT4NCiAgPC94czpjb21wbGV4VHlwZT4NCiAgPHhzOmVsZW1lbnQgbmFtZT0iTGlzdE9mTXVsdGlw +bGVWZWN0b3JzIiB0eXBlPSJ0bnM6TGlzdE9mTXVsdGlwbGVWZWN0b3JzIiBuaWxsYWJsZT0idHJ1 +ZSI+PC94czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJXb3JrT3JkZXJTdGF0 +dXNUeXBlIj4NCiAgICA8eHM6c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJBY3Rv +ciIgdHlwZT0ieHM6c3RyaW5nIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAg +ICAgPHhzOmVsZW1lbnQgbmFtZT0iVGltZXN0YW1wIiB0eXBlPSJ4czpkYXRlVGltZSIgbWluT2Nj +dXJzPSIwIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQ29tbWVudCIgdHlwZT0idWE6TG9j +YWxpemVkVGV4dCIgbWluT2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNl +cXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3Jk +ZXJTdGF0dXNUeXBlIiB0eXBlPSJ0bnM6V29ya09yZGVyU3RhdHVzVHlwZSIgLz4NCg0KICA8eHM6 +Y29tcGxleFR5cGUgbmFtZT0iTGlzdE9mV29ya09yZGVyU3RhdHVzVHlwZSI+DQogICAgPHhzOnNl +cXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgdHlw +ZT0idG5zOldvcmtPcmRlclN0YXR1c1R5cGUiIG1pbk9jY3Vycz0iMCIgbWF4T2NjdXJzPSJ1bmJv +dW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29t +cGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZldvcmtPcmRlclN0YXR1c1R5cGUi +IHR5cGU9InRuczpMaXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiBuaWxsYWJsZT0idHJ1ZSI+PC94 +czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJXb3JrT3JkZXJUeXBlIj4NCiAg +ICA8eHM6c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJJRCIgdHlwZT0idWE6R3Vp +ZCIgbWluT2NjdXJzPSIwIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQXNzZXRJRCIgdHlw +ZT0ieHM6c3RyaW5nIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iU3RhcnRUaW1lIiB0eXBlPSJ4czpkYXRlVGltZSIgbWluT2NjdXJzPSIw +IiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iU3RhdHVzQ29tbWVudHMiIHR5cGU9InRuczpM +aXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAv +Pg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50 +IG5hbWU9IldvcmtPcmRlclR5cGUiIHR5cGU9InRuczpXb3JrT3JkZXJUeXBlIiAvPg0KDQogIDx4 +czpjb21wbGV4VHlwZSBuYW1lPSJMaXN0T2ZXb3JrT3JkZXJUeXBlIj4NCiAgICA8eHM6c2VxdWVu +Y2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3JkZXJUeXBlIiB0eXBlPSJ0bnM6V29y +a09yZGVyVHlwZSIgbWluT2NjdXJzPSIwIiBtYXhPY2N1cnM9InVuYm91bmRlZCIgbmlsbGFibGU9 +InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4VHlwZT4NCiAgPHhz +OmVsZW1lbnQgbmFtZT0iTGlzdE9mV29ya09yZGVyVHlwZSIgdHlwZT0idG5zOkxpc3RPZldvcmtP +cmRlclR5cGUiIG5pbGxhYmxlPSJ0cnVlIj48L3hzOmVsZW1lbnQ+DQoNCjwveHM6c2NoZW1hPg== @@ -91365,5 +91652,2000 @@ czplbGVtZW50Pg0KDQo8L3hzOnNjaGVtYT4= 0 + + + ns=1;i=3582 + + Variable_2 + + 1 + VectorUnionValue + + + + VectorUnionValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1116 + + + + + i=40 + + false + + i=63 + + + + + i=37 + + false + + i=78 + + + + + + + + + + ns=1;i=3584 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3583 + + Variable_2 + + 1 + VectorWithOptionalFieldsValue + + + + VectorWithOptionalFieldsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1116 + + + + + i=40 + + false + + i=63 + + + + + i=37 + + false + + i=78 + + + + + + + + + + ns=1;i=3585 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3584 + + DataType_64 + + 1 + VectorUnion + + + + VectorUnion + + + 0 + 0 + + + 0 + + + + i=45 + + true + + i=12756 + + + + + i=38 + + false + + ns=1;i=3590 + + + + + i=38 + + false + + ns=1;i=3598 + + + + + i=38 + + false + + ns=1;i=3606 + + + + false + + + + + ns=1;i=3585 + + DataType_64 + + 1 + VectorWithOptionalFields + + + + VectorWithOptionalFields + + + 0 + 0 + + + 0 + + + + i=45 + + true + + i=22 + + + + + i=38 + + false + + ns=1;i=3591 + + + + + i=38 + + false + + ns=1;i=3599 + + + + + i=38 + + false + + ns=1;i=3607 + + + + false + + + + + ns=1;i=3586 + + Variable_2 + + 1 + VectorUnionValue + + + + VectorUnionValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1976 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3584 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3587 + + Variable_2 + + 1 + VectorWithOptionalFieldsValue + + + + VectorWithOptionalFieldsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1976 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3585 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3588 + + Variable_2 + + 1 + VectorUnionValue + + + + VectorUnionValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2740 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3584 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3589 + + Variable_2 + + 1 + VectorWithOptionalFieldsValue + + + + VectorWithOptionalFieldsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2740 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3585 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3590 + + Object_1 + + 0 + Default Binary + + + + Default Binary + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3584 + + + + + i=39 + + false + + ns=1;i=3592 + + + + 0 + + + + ns=1;i=3591 + + Object_1 + + 0 + Default Binary + + + + Default Binary + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3585 + + + + + i=39 + + false + + ns=1;i=3595 + + + + 0 + + + + ns=1;i=3592 + + Variable_2 + + 1 + VectorUnion + + + + VectorUnion + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=3518 + + + + + i=40 + + false + + i=69 + + + + + + VectorUnion + + + + i=12 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3595 + + Variable_2 + + 1 + VectorWithOptionalFields + + + + VectorWithOptionalFields + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=3518 + + + + + i=40 + + false + + i=69 + + + + + + VectorWithOptionalFields + + + + i=12 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3598 + + Object_1 + + 0 + Default XML + + + + Default XML + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3584 + + + + + i=39 + + false + + ns=1;i=3600 + + + + 0 + + + + ns=1;i=3599 + + Object_1 + + 0 + Default XML + + + + Default XML + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3585 + + + + + i=39 + + false + + ns=1;i=3603 + + + + 0 + + + + ns=1;i=3600 + + Variable_2 + + 1 + VectorUnion + + + + VectorUnion + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=3550 + + + + + i=40 + + false + + i=69 + + + + + + //xs:element[@name='VectorUnion'] + + + + i=12 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3603 + + Variable_2 + + 1 + VectorWithOptionalFields + + + + VectorWithOptionalFields + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=3550 + + + + + i=40 + + false + + i=69 + + + + + + //xs:element[@name='VectorWithOptionalFields'] + + + + i=12 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3606 + + Object_1 + + 0 + Default JSON + + + + Default JSON + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3584 + + + + 0 + + + + ns=1;i=3607 + + Object_1 + + 0 + Default JSON + + + + Default JSON + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3585 + + + + 0 + + + + ns=1;i=3608 + + Variable_2 + + 1 + VectorUnionValue + + + + VectorUnionValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1456 + + + + + i=40 + + false + + i=63 + + + + + i=37 + + false + + i=78 + + + + + + + + + + ns=1;i=3584 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3609 + + Variable_2 + + 1 + VectorWithOptionalFieldsValue + + + + VectorWithOptionalFieldsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1456 + + + + + i=40 + + false + + i=63 + + + + + i=37 + + false + + i=78 + + + + + + + + + + ns=1;i=3585 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3610 + + Variable_2 + + 1 + VectorUnionValue + + + + VectorUnionValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2165 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3584 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3611 + + Variable_2 + + 1 + VectorWithOptionalFieldsValue + + + + VectorWithOptionalFieldsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2165 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3585 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3612 + + Variable_2 + + 1 + VectorUnionValue + + + + VectorUnionValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2929 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3584 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3613 + + Variable_2 + + 1 + VectorWithOptionalFieldsValue + + + + VectorWithOptionalFieldsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2929 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3585 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3614 + + Variable_2 + + 1 + MultipleVectorsValue + + + + MultipleVectorsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1116 + + + + + i=40 + + false + + i=63 + + + + + i=37 + + false + + i=78 + + + + + + + + + + ns=1;i=3615 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3615 + + DataType_64 + + 1 + MultipleVectors + + + + MultipleVectors + + + 0 + 0 + + + 0 + + + + i=45 + + true + + i=22 + + + + + i=38 + + false + + ns=1;i=3618 + + + + + i=38 + + false + + ns=1;i=3622 + + + + + i=38 + + false + + ns=1;i=3626 + + + + false + + + + + ns=1;i=3616 + + Variable_2 + + 1 + MultipleVectorsValue + + + + MultipleVectorsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1976 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3615 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3617 + + Variable_2 + + 1 + MultipleVectorsValue + + + + MultipleVectorsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2740 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3615 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3618 + + Object_1 + + 0 + Default Binary + + + + Default Binary + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3615 + + + + + i=39 + + false + + ns=1;i=3619 + + + + 0 + + + + ns=1;i=3619 + + Variable_2 + + 1 + MultipleVectors + + + + MultipleVectors + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=3518 + + + + + i=40 + + false + + i=69 + + + + + + MultipleVectors + + + + i=12 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3622 + + Object_1 + + 0 + Default XML + + + + Default XML + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3615 + + + + + i=39 + + false + + ns=1;i=3623 + + + + 0 + + + + ns=1;i=3623 + + Variable_2 + + 1 + MultipleVectors + + + + MultipleVectors + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=3550 + + + + + i=40 + + false + + i=69 + + + + + + //xs:element[@name='MultipleVectors'] + + + + i=12 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3626 + + Object_1 + + 0 + Default JSON + + + + Default JSON + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3615 + + + + 0 + + + + ns=1;i=3627 + + Variable_2 + + 1 + MultipleVectorsValue + + + + MultipleVectorsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1456 + + + + + i=40 + + false + + i=63 + + + + + i=37 + + false + + i=78 + + + + + + + + + + ns=1;i=3615 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3628 + + Variable_2 + + 1 + MultipleVectorsValue + + + + MultipleVectorsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2165 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3615 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3629 + + Variable_2 + + 1 + MultipleVectorsValue + + + + MultipleVectorsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2929 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3615 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + \ No newline at end of file diff --git a/Applications/Quickstarts.Servers/TestData/TestData.NodeSet2.xml b/Applications/Quickstarts.Servers/TestData/TestData.NodeSet2.xml index dca13f191..cf9e37126 100644 --- a/Applications/Quickstarts.Servers/TestData/TestData.NodeSet2.xml +++ b/Applications/Quickstarts.Servers/TestData/TestData.NodeSet2.xml @@ -1,10 +1,10 @@  - + http://test.org/UA/Data/ - + @@ -811,6 +811,9 @@ ns=1;i=1204 ns=1;i=1205 ns=1;i=1206 + ns=1;i=3582 + ns=1;i=3583 + ns=1;i=3614 ns=1;i=1015 @@ -1065,6 +1068,30 @@ ns=1;i=1206 + + VectorUnionValue + + i=63 + i=78 + ns=1;i=1116 + + + + VectorWithOptionalFieldsValue + + i=63 + i=78 + ns=1;i=1116 + + + + MultipleVectorsValue + + i=63 + i=78 + ns=1;i=1116 + + StructureValueObjectType @@ -1665,6 +1692,9 @@ ns=1;i=1544 ns=1;i=1545 ns=1;i=1546 + ns=1;i=3608 + ns=1;i=3609 + ns=1;i=3627 ns=1;i=1015 @@ -1892,6 +1922,30 @@ ns=1;i=1456 + + VectorUnionValue + + i=63 + i=78 + ns=1;i=1456 + + + + VectorWithOptionalFieldsValue + + i=63 + i=78 + ns=1;i=1456 + + + + MultipleVectorsValue + + i=63 + i=78 + ns=1;i=1456 + + AnalogArrayValueObjectType @@ -2776,6 +2830,42 @@ ns=1;i=1889 + + VectorUnion + + i=12756 + + + + + + + + + VectorWithOptionalFields + + i=22 + + + + + + + + + MultipleVectors + + i=22 + + + + + + + + + + WorkOrderStatusType @@ -6169,6 +6259,9 @@ ns=1;i=2064 ns=1;i=2065 ns=1;i=2066 + ns=1;i=3586 + ns=1;i=3587 + ns=1;i=3616 ns=1;i=1975 ns=1;i=1980 ns=1;i=1116 @@ -6770,6 +6863,27 @@ ns=1;i=2066 + + VectorUnionValue + + i=63 + ns=1;i=1976 + + + + VectorWithOptionalFieldsValue + + i=63 + ns=1;i=1976 + + + + MultipleVectorsValue + + i=63 + ns=1;i=1976 + + Structure @@ -7446,6 +7560,9 @@ ns=1;i=2253 ns=1;i=2254 ns=1;i=2255 + ns=1;i=3610 + ns=1;i=3611 + ns=1;i=3628 ns=1;i=1975 ns=1;i=2169 ns=1;i=1456 @@ -8023,6 +8140,27 @@ ns=1;i=2165 + + VectorUnionValue + + i=63 + ns=1;i=2165 + + + + VectorWithOptionalFieldsValue + + i=63 + ns=1;i=2165 + + + + MultipleVectorsValue + + i=63 + ns=1;i=2165 + + UserScalar @@ -13647,6 +13785,9 @@ ns=1;i=2828 ns=1;i=2829 ns=1;i=2830 + ns=1;i=3588 + ns=1;i=3589 + ns=1;i=3617 ns=1;i=2739 ns=1;i=2744 ns=1;i=1116 @@ -14251,6 +14392,27 @@ ns=1;i=2830 + + VectorUnionValue + + i=63 + ns=1;i=2740 + + + + VectorWithOptionalFieldsValue + + i=63 + ns=1;i=2740 + + + + MultipleVectorsValue + + i=63 + ns=1;i=2740 + + Structure @@ -14930,6 +15092,9 @@ ns=1;i=3017 ns=1;i=3018 ns=1;i=3019 + ns=1;i=3612 + ns=1;i=3613 + ns=1;i=3629 ns=1;i=2739 ns=1;i=2933 ns=1;i=1456 @@ -15510,6 +15675,27 @@ ns=1;i=2929 + + VectorUnionValue + + i=63 + ns=1;i=2929 + + + + VectorWithOptionalFieldsValue + + i=63 + ns=1;i=2929 + + + + MultipleVectorsValue + + i=63 + ns=1;i=2929 + + UserScalar @@ -18147,6 +18333,30 @@ i=76 + + Default Binary + + ns=1;i=3584 + ns=1;i=3592 + i=76 + + + + Default Binary + + ns=1;i=3585 + ns=1;i=3595 + i=76 + + + + Default Binary + + ns=1;i=3615 + ns=1;i=3619 + i=76 + + Default Binary @@ -18173,6 +18383,9 @@ ns=1;i=3528 ns=1;i=3531 ns=1;i=3534 + ns=1;i=3592 + ns=1;i=3595 + ns=1;i=3619 ns=1;i=3537 ns=1;i=3540 i=93 @@ -18418,20 +18631,44 @@ IE5hbWU9IlZlY3RvciIgQmFzZVR5cGU9InVhOkV4dGVuc2lvbk9iamVjdCI+DQogICAgPG9wYzpG aWVsZCBOYW1lPSJYIiBUeXBlTmFtZT0ib3BjOkRvdWJsZSIgLz4NCiAgICA8b3BjOkZpZWxkIE5h bWU9IlkiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iWiIg VHlwZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQogIDxv -cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgQmFzZVR5cGU9InVh -OkV4dGVuc2lvbk9iamVjdCI+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJBY3RvciIgVHlwZU5hbWU9 -Im9wYzpTdHJpbmciIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJUaW1lc3RhbXAiIFR5cGVOYW1l -PSJvcGM6RGF0ZVRpbWUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJDb21tZW50IiBUeXBlTmFt -ZT0idWE6TG9jYWxpemVkVGV4dCIgLz4NCiAgPC9vcGM6U3RydWN0dXJlZFR5cGU+DQoNCiAgPG9w -YzpTdHJ1Y3R1cmVkVHlwZSBOYW1lPSJXb3JrT3JkZXJUeXBlIiBCYXNlVHlwZT0idWE6RXh0ZW5z -aW9uT2JqZWN0Ij4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IklEIiBUeXBlTmFtZT0ib3BjOkd1aWQi -IC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJBc3NldElEIiBUeXBlTmFtZT0ib3BjOlN0cmluZyIg -Lz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlN0YXJ0VGltZSIgVHlwZU5hbWU9Im9wYzpEYXRlVGlt -ZSIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9Ik5vT2ZTdGF0dXNDb21tZW50cyIgVHlwZU5hbWU9 -Im9wYzpJbnQzMiIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlN0YXR1c0NvbW1lbnRzIiBUeXBl -TmFtZT0idG5zOldvcmtPcmRlclN0YXR1c1R5cGUiIExlbmd0aEZpZWxkPSJOb09mU3RhdHVzQ29t -bWVudHMiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQo8L29wYzpUeXBlRGljdGlvbmFy -eT4= +cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iVmVjdG9yVW5pb24iIEJhc2VUeXBlPSJ1YTpVbmlvbiI+ +DQogICAgPG9wYzpGaWVsZCBOYW1lPSJYIiBUeXBlTmFtZT0ib3BjOkRvdWJsZSIgLz4NCiAgICA8 +b3BjOkZpZWxkIE5hbWU9IlkiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6Rmll +bGQgTmFtZT0iWiIgVHlwZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRU +eXBlPg0KDQogIDxvcGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmll +bGRzIiBCYXNlVHlwZT0idWE6RXh0ZW5zaW9uT2JqZWN0Ij4NCiAgICA8b3BjOkZpZWxkIE5hbWU9 +IlgiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iWSIgVHlw +ZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJaIiBUeXBlTmFtZT0i +b3BjOkRvdWJsZSIgLz4NCiAgPC9vcGM6U3RydWN0dXJlZFR5cGU+DQoNCiAgPG9wYzpTdHJ1Y3R1 +cmVkVHlwZSBOYW1lPSJNdWx0aXBsZVZlY3RvcnMiIEJhc2VUeXBlPSJ1YTpFeHRlbnNpb25PYmpl +Y3QiPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yIiBUeXBlTmFtZT0idG5zOlZlY3RvciIg +Lz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlZlY3RvclVuaW9uIiBUeXBlTmFtZT0idG5zOlZlY3Rv +clVuaW9uIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRz +IiBUeXBlTmFtZT0idG5zOlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyIgLz4NCiAgICA8b3BjOkZp +ZWxkIE5hbWU9Ik5vT2ZWZWN0b3JBcnJheSIgVHlwZU5hbWU9Im9wYzpJbnQzMiIgLz4NCiAgICA8 +b3BjOkZpZWxkIE5hbWU9IlZlY3RvckFycmF5IiBUeXBlTmFtZT0idG5zOlZlY3RvciIgTGVuZ3Ro +RmllbGQ9Ik5vT2ZWZWN0b3JBcnJheSIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9Ik5vT2ZWZWN0 +b3JVbmlvbkFycmF5IiBUeXBlTmFtZT0ib3BjOkludDMyIiAvPg0KICAgIDxvcGM6RmllbGQgTmFt +ZT0iVmVjdG9yVW5pb25BcnJheSIgVHlwZU5hbWU9InRuczpWZWN0b3JVbmlvbiIgTGVuZ3RoRmll +bGQ9Ik5vT2ZWZWN0b3JVbmlvbkFycmF5IiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iTm9PZlZl +Y3RvcldpdGhPcHRpb25hbEZpZWxkc0FycmF5IiBUeXBlTmFtZT0ib3BjOkludDMyIiAvPg0KICAg +IDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzQXJyYXkiIFR5cGVOYW1l +PSJ0bnM6VmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzIiBMZW5ndGhGaWVsZD0iTm9PZlZlY3Rvcldp +dGhPcHRpb25hbEZpZWxkc0FycmF5IiAvPg0KICA8L29wYzpTdHJ1Y3R1cmVkVHlwZT4NCg0KICA8 +b3BjOlN0cnVjdHVyZWRUeXBlIE5hbWU9IldvcmtPcmRlclN0YXR1c1R5cGUiIEJhc2VUeXBlPSJ1 +YTpFeHRlbnNpb25PYmplY3QiPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQWN0b3IiIFR5cGVOYW1l +PSJvcGM6U3RyaW5nIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVGltZXN0YW1wIiBUeXBlTmFt +ZT0ib3BjOkRhdGVUaW1lIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQ29tbWVudCIgVHlwZU5h +bWU9InVhOkxvY2FsaXplZFRleHQiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQogIDxv +cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iV29ya09yZGVyVHlwZSIgQmFzZVR5cGU9InVhOkV4dGVu +c2lvbk9iamVjdCI+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJJRCIgVHlwZU5hbWU9Im9wYzpHdWlk +IiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQXNzZXRJRCIgVHlwZU5hbWU9Im9wYzpTdHJpbmci +IC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJTdGFydFRpbWUiIFR5cGVOYW1lPSJvcGM6RGF0ZVRp +bWUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJOb09mU3RhdHVzQ29tbWVudHMiIFR5cGVOYW1l +PSJvcGM6SW50MzIiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJTdGF0dXNDb21tZW50cyIgVHlw +ZU5hbWU9InRuczpXb3JrT3JkZXJTdGF0dXNUeXBlIiBMZW5ndGhGaWVsZD0iTm9PZlN0YXR1c0Nv +bW1lbnRzIiAvPg0KICA8L29wYzpTdHJ1Y3R1cmVkVHlwZT4NCg0KPC9vcGM6VHlwZURpY3Rpb25h +cnk+ @@ -18504,6 +18741,36 @@ eT4= Vector + + VectorUnion + + i=69 + ns=1;i=3518 + + + VectorUnion + + + + VectorWithOptionalFields + + i=69 + ns=1;i=3518 + + + VectorWithOptionalFields + + + + MultipleVectors + + i=69 + ns=1;i=3518 + + + MultipleVectors + + WorkOrderStatusType @@ -18564,6 +18831,30 @@ eT4= i=76 + + Default XML + + ns=1;i=3584 + ns=1;i=3600 + i=76 + + + + Default XML + + ns=1;i=3585 + ns=1;i=3603 + i=76 + + + + Default XML + + ns=1;i=3615 + ns=1;i=3623 + i=76 + + Default XML @@ -18590,6 +18881,9 @@ eT4= ns=1;i=3560 ns=1;i=3563 ns=1;i=3566 + ns=1;i=3600 + ns=1;i=3603 + ns=1;i=3623 ns=1;i=3569 ns=1;i=3572 i=92 @@ -18602,7 +18896,7 @@ c2QiDQogIHhtbG5zOnRucz0iaHR0cDovL3Rlc3Qub3JnL1VBL0RhdGEvIg0KICB0YXJnZXROYW1l c3BhY2U9Imh0dHA6Ly90ZXN0Lm9yZy9VQS9EYXRhLyINCiAgZWxlbWVudEZvcm1EZWZhdWx0PSJx dWFsaWZpZWQiDQo+DQogIDx4czphbm5vdGF0aW9uPg0KICAgIDx4czphcHBpbmZvPg0KICAgICAg PHVhOk1vZGVsIE1vZGVsVXJpPSJodHRwOi8vdGVzdC5vcmcvVUEvRGF0YS8iIFZlcnNpb249IjEu -MC4wIiBQdWJsaWNhdGlvbkRhdGU9IjIwMjMtMDYtMDZUMDY6MzM6NTEuNTc2OTYyOVoiIC8+DQog +MC4wIiBQdWJsaWNhdGlvbkRhdGU9IjIwMjMtMTAtMDlUMDg6NTE6MjQuNTIzNzA4OFoiIC8+DQog ICAgPC94czphcHBpbmZvPg0KICA8L3hzOmFubm90YXRpb24+DQogIA0KICA8eHM6aW1wb3J0IG5h bWVzcGFjZT0iaHR0cDovL29wY2ZvdW5kYXRpb24ub3JnL1VBLzIwMDgvMDIvVHlwZXMueHNkIiAv Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJTY2FsYXJTdHJ1Y3R1cmVEYXRhVHlwZSI+DQog @@ -18841,35 +19135,82 @@ YW1lPSJWZWN0b3IiIHR5cGU9InRuczpWZWN0b3IiIG1pbk9jY3Vycz0iMCIgbWF4T2NjdXJzPSJ1 bmJvdW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6 Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZlZlY3RvciIgdHlwZT0idG5z Okxpc3RPZlZlY3RvciIgbmlsbGFibGU9InRydWUiPjwveHM6ZWxlbWVudD4NCg0KICA8eHM6Y29t -cGxleFR5cGUgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0K -ICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQWN0b3IiIHR5cGU9InhzOnN0cmluZyIgbWluT2NjdXJz -PSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlRpbWVzdGFt -cCIgdHlwZT0ieHM6ZGF0ZVRpbWUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4czplbGVtZW50 -IG5hbWU9IkNvbW1lbnQiIHR5cGU9InVhOkxvY2FsaXplZFRleHQiIG1pbk9jY3Vycz0iMCIgbmls -bGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4VHlwZT4N -CiAgPHhzOmVsZW1lbnQgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgdHlwZT0idG5zOldvcmtP -cmRlclN0YXR1c1R5cGUiIC8+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RPZldvcmtP -cmRlclN0YXR1c1R5cGUiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4czplbGVtZW50IG5h -bWU9IldvcmtPcmRlclN0YXR1c1R5cGUiIHR5cGU9InRuczpXb3JrT3JkZXJTdGF0dXNUeXBlIiBt -aW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAg -ICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1l -PSJMaXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiB0eXBlPSJ0bnM6TGlzdE9mV29ya09yZGVyU3Rh -dHVzVHlwZSIgbmlsbGFibGU9InRydWUiPjwveHM6ZWxlbWVudD4NCg0KICA8eHM6Y29tcGxleFR5 -cGUgbmFtZT0iV29ya09yZGVyVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVs -ZW1lbnQgbmFtZT0iSUQiIHR5cGU9InVhOkd1aWQiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4 -czplbGVtZW50IG5hbWU9IkFzc2V0SUQiIHR5cGU9InhzOnN0cmluZyIgbWluT2NjdXJzPSIwIiBu -aWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlN0YXJ0VGltZSIgdHlw -ZT0ieHM6ZGF0ZVRpbWUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9 -IlN0YXR1c0NvbW1lbnRzIiB0eXBlPSJ0bnM6TGlzdE9mV29ya09yZGVyU3RhdHVzVHlwZSIgbWlu -T2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hz -OmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3JkZXJUeXBlIiB0eXBlPSJ0 -bnM6V29ya09yZGVyVHlwZSIgLz4NCg0KICA8eHM6Y29tcGxleFR5cGUgbmFtZT0iTGlzdE9mV29y -a09yZGVyVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0i -V29ya09yZGVyVHlwZSIgdHlwZT0idG5zOldvcmtPcmRlclR5cGUiIG1pbk9jY3Vycz0iMCIgbWF4 -T2NjdXJzPSJ1bmJvdW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+ -DQogIDwveHM6Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZldvcmtPcmRl -clR5cGUiIHR5cGU9InRuczpMaXN0T2ZXb3JrT3JkZXJUeXBlIiBuaWxsYWJsZT0idHJ1ZSI+PC94 -czplbGVtZW50Pg0KDQo8L3hzOnNjaGVtYT4= +cGxleFR5cGUgbmFtZT0iVmVjdG9yVW5pb24iPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4 +czplbGVtZW50IG5hbWU9IlN3aXRjaEZpZWxkIiB0eXBlPSJ4czp1bnNpZ25lZEludCIgbWluT2Nj +dXJzPSIwIiAvPg0KICAgICAgPHhzOmNob2ljZT4NCiAgICAgICAgPHhzOmVsZW1lbnQgbmFtZT0i +WCIgdHlwZT0ieHM6ZG91YmxlIiBtaW5PY2N1cnM9IjAiIC8+DQogICAgICAgIDx4czplbGVtZW50 +IG5hbWU9IlkiIHR5cGU9InhzOmRvdWJsZSIgbWluT2NjdXJzPSIwIiAvPg0KICAgICAgICA8eHM6 +ZWxlbWVudCBuYW1lPSJaIiB0eXBlPSJ4czpkb3VibGUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAg +IDwveHM6Y2hvaWNlPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29tcGxleFR5cGU+DQog +IDx4czplbGVtZW50IG5hbWU9IlZlY3RvclVuaW9uIiB0eXBlPSJ0bnM6VmVjdG9yVW5pb24iIC8+ +DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RPZlZlY3RvclVuaW9uIj4NCiAgICA8eHM6 +c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJWZWN0b3JVbmlvbiIgdHlwZT0idG5z +OlZlY3RvclVuaW9uIiBtaW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBuaWxsYWJs +ZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8 +eHM6ZWxlbWVudCBuYW1lPSJMaXN0T2ZWZWN0b3JVbmlvbiIgdHlwZT0idG5zOkxpc3RPZlZlY3Rv +clVuaW9uIiBuaWxsYWJsZT0idHJ1ZSI+PC94czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlw +ZSBuYW1lPSJWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAg +ICAgIDx4czplbGVtZW50IG5hbWU9IlgiIHR5cGU9InhzOmRvdWJsZSIgbWluT2NjdXJzPSIwIiAv +Pg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iWSIgdHlwZT0ieHM6ZG91YmxlIiBtaW5PY2N1cnM9 +IjAiIC8+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJaIiB0eXBlPSJ4czpkb3VibGUiIG1pbk9j +Y3Vycz0iMCIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8 +eHM6ZWxlbWVudCBuYW1lPSJWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIHR5cGU9InRuczpWZWN0 +b3JXaXRoT3B0aW9uYWxGaWVsZHMiIC8+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RP +ZlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzIiB0eXBlPSJ0bnM6VmVjdG9y +V2l0aE9wdGlvbmFsRmllbGRzIiBtaW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBu +aWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBl +Pg0KICA8eHM6ZWxlbWVudCBuYW1lPSJMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIHR5 +cGU9InRuczpMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIG5pbGxhYmxlPSJ0cnVlIj48 +L3hzOmVsZW1lbnQ+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ik11bHRpcGxlVmVjdG9ycyI+ +DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yIiB0eXBl +PSJ0bnM6VmVjdG9yIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iVmVjdG9yVW5pb24iIHR5cGU9InRuczpWZWN0b3JVbmlvbiIgbWluT2Nj +dXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlZlY3Rv +cldpdGhPcHRpb25hbEZpZWxkcyIgdHlwZT0idG5zOlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyIg +bWluT2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9 +IlZlY3RvckFycmF5IiB0eXBlPSJ0bnM6TGlzdE9mVmVjdG9yIiBtaW5PY2N1cnM9IjAiIG5pbGxh +YmxlPSJ0cnVlIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yVW5pb25BcnJheSIg +dHlwZT0idG5zOkxpc3RPZlZlY3RvclVuaW9uIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVl +IiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzQXJy +YXkiIHR5cGU9InRuczpMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIG1pbk9jY3Vycz0i +MCIgbmlsbGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4 +VHlwZT4NCiAgPHhzOmVsZW1lbnQgbmFtZT0iTXVsdGlwbGVWZWN0b3JzIiB0eXBlPSJ0bnM6TXVs +dGlwbGVWZWN0b3JzIiAvPg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJMaXN0T2ZNdWx0aXBs +ZVZlY3RvcnMiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9Ik11 +bHRpcGxlVmVjdG9ycyIgdHlwZT0idG5zOk11bHRpcGxlVmVjdG9ycyIgbWluT2NjdXJzPSIwIiBt +YXhPY2N1cnM9InVuYm91bmRlZCIgbmlsbGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5j +ZT4NCiAgPC94czpjb21wbGV4VHlwZT4NCiAgPHhzOmVsZW1lbnQgbmFtZT0iTGlzdE9mTXVsdGlw +bGVWZWN0b3JzIiB0eXBlPSJ0bnM6TGlzdE9mTXVsdGlwbGVWZWN0b3JzIiBuaWxsYWJsZT0idHJ1 +ZSI+PC94czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJXb3JrT3JkZXJTdGF0 +dXNUeXBlIj4NCiAgICA8eHM6c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJBY3Rv +ciIgdHlwZT0ieHM6c3RyaW5nIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAg +ICAgPHhzOmVsZW1lbnQgbmFtZT0iVGltZXN0YW1wIiB0eXBlPSJ4czpkYXRlVGltZSIgbWluT2Nj +dXJzPSIwIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQ29tbWVudCIgdHlwZT0idWE6TG9j +YWxpemVkVGV4dCIgbWluT2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNl +cXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3Jk +ZXJTdGF0dXNUeXBlIiB0eXBlPSJ0bnM6V29ya09yZGVyU3RhdHVzVHlwZSIgLz4NCg0KICA8eHM6 +Y29tcGxleFR5cGUgbmFtZT0iTGlzdE9mV29ya09yZGVyU3RhdHVzVHlwZSI+DQogICAgPHhzOnNl +cXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgdHlw +ZT0idG5zOldvcmtPcmRlclN0YXR1c1R5cGUiIG1pbk9jY3Vycz0iMCIgbWF4T2NjdXJzPSJ1bmJv +dW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29t +cGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZldvcmtPcmRlclN0YXR1c1R5cGUi +IHR5cGU9InRuczpMaXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiBuaWxsYWJsZT0idHJ1ZSI+PC94 +czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJXb3JrT3JkZXJUeXBlIj4NCiAg +ICA8eHM6c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJJRCIgdHlwZT0idWE6R3Vp +ZCIgbWluT2NjdXJzPSIwIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQXNzZXRJRCIgdHlw +ZT0ieHM6c3RyaW5nIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iU3RhcnRUaW1lIiB0eXBlPSJ4czpkYXRlVGltZSIgbWluT2NjdXJzPSIw +IiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iU3RhdHVzQ29tbWVudHMiIHR5cGU9InRuczpM +aXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAv +Pg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50 +IG5hbWU9IldvcmtPcmRlclR5cGUiIHR5cGU9InRuczpXb3JrT3JkZXJUeXBlIiAvPg0KDQogIDx4 +czpjb21wbGV4VHlwZSBuYW1lPSJMaXN0T2ZXb3JrT3JkZXJUeXBlIj4NCiAgICA8eHM6c2VxdWVu +Y2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3JkZXJUeXBlIiB0eXBlPSJ0bnM6V29y +a09yZGVyVHlwZSIgbWluT2NjdXJzPSIwIiBtYXhPY2N1cnM9InVuYm91bmRlZCIgbmlsbGFibGU9 +InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4VHlwZT4NCiAgPHhz +OmVsZW1lbnQgbmFtZT0iTGlzdE9mV29ya09yZGVyVHlwZSIgdHlwZT0idG5zOkxpc3RPZldvcmtP +cmRlclR5cGUiIG5pbGxhYmxlPSJ0cnVlIj48L3hzOmVsZW1lbnQ+DQoNCjwveHM6c2NoZW1hPg== @@ -18942,6 +19283,36 @@ czplbGVtZW50Pg0KDQo8L3hzOnNjaGVtYT4= //xs:element[@name='Vector'] + + VectorUnion + + i=69 + ns=1;i=3550 + + + //xs:element[@name='VectorUnion'] + + + + VectorWithOptionalFields + + i=69 + ns=1;i=3550 + + + //xs:element[@name='VectorWithOptionalFields'] + + + + MultipleVectors + + i=69 + ns=1;i=3550 + + + //xs:element[@name='MultipleVectors'] + + WorkOrderStatusType @@ -18997,6 +19368,27 @@ czplbGVtZW50Pg0KDQo8L3hzOnNjaGVtYT4= i=76 + + Default JSON + + ns=1;i=3584 + i=76 + + + + Default JSON + + ns=1;i=3585 + i=76 + + + + Default JSON + + ns=1;i=3615 + i=76 + + Default JSON diff --git a/Applications/Quickstarts.Servers/TestData/TestData.PredefinedNodes.uanodes b/Applications/Quickstarts.Servers/TestData/TestData.PredefinedNodes.uanodes index 1a970df54..9f0c6d45a 100644 Binary files a/Applications/Quickstarts.Servers/TestData/TestData.PredefinedNodes.uanodes and b/Applications/Quickstarts.Servers/TestData/TestData.PredefinedNodes.uanodes differ diff --git a/Applications/Quickstarts.Servers/TestData/TestData.PredefinedNodes.xml b/Applications/Quickstarts.Servers/TestData/TestData.PredefinedNodes.xml index 22245b979..7fa8b2f0c 100644 --- a/Applications/Quickstarts.Servers/TestData/TestData.PredefinedNodes.xml +++ b/Applications/Quickstarts.Servers/TestData/TestData.PredefinedNodes.xml @@ -2948,6 +2948,84 @@ 1 + + Variable_2 + + ns=1;i=3582 + + + 1 + VectorUnionValue + + + i=47 + + + i=63 + + + i=78 + + 3582 + + ns=1;i=3584 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3583 + + + 1 + VectorWithOptionalFieldsValue + + + i=47 + + + i=63 + + + i=78 + + 3583 + + ns=1;i=3585 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3614 + + + 1 + MultipleVectorsValue + + + i=47 + + + i=63 + + + i=78 + + 3614 + + ns=1;i=3615 + + -1 + 1 + 1 + ObjectType_8 @@ -5603,6 +5681,87 @@ 1 1 + + Variable_2 + + ns=1;i=3608 + + + 1 + VectorUnionValue + + + i=47 + + + i=63 + + + i=78 + + 3608 + + ns=1;i=3584 + + 1 + 0 + 1 + 1 + + + Variable_2 + + ns=1;i=3609 + + + 1 + VectorWithOptionalFieldsValue + + + i=47 + + + i=63 + + + i=78 + + 3609 + + ns=1;i=3585 + + 1 + 0 + 1 + 1 + + + Variable_2 + + ns=1;i=3627 + + + 1 + MultipleVectorsValue + + + i=47 + + + i=63 + + + i=78 + + 3627 + + ns=1;i=3615 + + 1 + 0 + 1 + 1 + ObjectType_8 @@ -8476,6 +8635,216 @@ 1 + + DataType_64 + + ns=1;i=3584 + + + 1 + VectorUnion + + + i=12756 + + + + i=14798 + + + + + i=12756 + + Union_2 + + + X + + i=11 + + -1 + + 0 + false + + + Y + + i=11 + + -1 + + 0 + false + + + Z + + i=11 + + -1 + + 0 + false + + + + + + + + DataType_64 + + ns=1;i=3585 + + + 1 + VectorWithOptionalFields + + + i=22 + + + + i=14798 + + + + + i=22 + + StructureWithOptionalFields_1 + + + X + + i=11 + + -1 + + 0 + true + + + Y + + i=11 + + -1 + + 0 + true + + + Z + + i=11 + + -1 + + 0 + true + + + + + + + + DataType_64 + + ns=1;i=3615 + + + 1 + MultipleVectors + + + i=22 + + + + i=14798 + + + + + i=22 + + Structure_0 + + + Vector + + ns=1;i=1888 + + -1 + + 0 + false + + + VectorUnion + + ns=1;i=3584 + + -1 + + 0 + false + + + VectorWithOptionalFields + + ns=1;i=3585 + + -1 + + 0 + false + + + VectorArray + + ns=1;i=1888 + + 1 + + 0 + + 0 + false + + + VectorUnionArray + + ns=1;i=3584 + + 1 + + 0 + + 0 + false + + + VectorWithOptionalFieldsArray + + ns=1;i=3585 + + 1 + + 0 + + 0 + false + + + + + + DataType_64 @@ -14275,6 +14644,75 @@ 1 + + Variable_2 + + ns=1;i=3586 + + + 1 + VectorUnionValue + + + i=47 + + + i=63 + + 3586 + + ns=1;i=3584 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3587 + + + 1 + VectorWithOptionalFieldsValue + + + i=47 + + + i=63 + + 3587 + + ns=1;i=3585 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3616 + + + 1 + MultipleVectorsValue + + + i=47 + + + i=63 + + 3616 + + ns=1;i=3615 + + -1 + 1 + 1 + Object_1 @@ -17646,6 +18084,78 @@ 1 1 + + Variable_2 + + ns=1;i=3610 + + + 1 + VectorUnionValue + + + i=47 + + + i=63 + + 3610 + + ns=1;i=3584 + + 1 + 0 + 1 + 1 + + + Variable_2 + + ns=1;i=3611 + + + 1 + VectorWithOptionalFieldsValue + + + i=47 + + + i=63 + + 3611 + + ns=1;i=3585 + + 1 + 0 + 1 + 1 + + + Variable_2 + + ns=1;i=3628 + + + 1 + MultipleVectorsValue + + + i=47 + + + i=63 + + 3628 + + ns=1;i=3615 + + 1 + 0 + 1 + 1 + Object_1 @@ -29297,6 +29807,75 @@ 1 + + Variable_2 + + ns=1;i=3588 + + + 1 + VectorUnionValue + + + i=47 + + + i=63 + + 3588 + + ns=1;i=3584 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3589 + + + 1 + VectorWithOptionalFieldsValue + + + i=47 + + + i=63 + + 3589 + + ns=1;i=3585 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3617 + + + 1 + MultipleVectorsValue + + + i=47 + + + i=63 + + 3617 + + ns=1;i=3615 + + -1 + 1 + 1 + Object_1 @@ -32678,6 +33257,78 @@ 1 1 + + Variable_2 + + ns=1;i=3612 + + + 1 + VectorUnionValue + + + i=47 + + + i=63 + + 3612 + + ns=1;i=3584 + + 1 + 0 + 1 + 1 + + + Variable_2 + + ns=1;i=3613 + + + 1 + VectorWithOptionalFieldsValue + + + i=47 + + + i=63 + + 3613 + + ns=1;i=3585 + + 1 + 0 + 1 + 1 + + + Variable_2 + + ns=1;i=3629 + + + 1 + MultipleVectorsValue + + + i=47 + + + i=63 + + 3629 + + ns=1;i=3615 + + 1 + 0 + 1 + 1 + Object_1 @@ -39755,6 +40406,105 @@ + + Object_1 + + ns=1;i=3590 + + + 0 + Default Binary + + + i=76 + + 3590 + + + + i=38 + + true + + ns=1;i=3584 + + + + + i=39 + + + ns=1;i=3592 + + + + + + Object_1 + + ns=1;i=3591 + + + 0 + Default Binary + + + i=76 + + 3591 + + + + i=38 + + true + + ns=1;i=3585 + + + + + i=39 + + + ns=1;i=3595 + + + + + + Object_1 + + ns=1;i=3618 + + + 0 + Default Binary + + + i=76 + + 3618 + + + + i=38 + + true + + ns=1;i=3615 + + + + + i=39 + + + ns=1;i=3619 + + + + Object_1 @@ -40075,20 +40825,44 @@ IE5hbWU9IlZlY3RvciIgQmFzZVR5cGU9InVhOkV4dGVuc2lvbk9iamVjdCI+DQogICAgPG9wYzpG aWVsZCBOYW1lPSJYIiBUeXBlTmFtZT0ib3BjOkRvdWJsZSIgLz4NCiAgICA8b3BjOkZpZWxkIE5h bWU9IlkiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iWiIg VHlwZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQogIDxv -cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgQmFzZVR5cGU9InVh -OkV4dGVuc2lvbk9iamVjdCI+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJBY3RvciIgVHlwZU5hbWU9 -Im9wYzpTdHJpbmciIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJUaW1lc3RhbXAiIFR5cGVOYW1l -PSJvcGM6RGF0ZVRpbWUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJDb21tZW50IiBUeXBlTmFt -ZT0idWE6TG9jYWxpemVkVGV4dCIgLz4NCiAgPC9vcGM6U3RydWN0dXJlZFR5cGU+DQoNCiAgPG9w -YzpTdHJ1Y3R1cmVkVHlwZSBOYW1lPSJXb3JrT3JkZXJUeXBlIiBCYXNlVHlwZT0idWE6RXh0ZW5z -aW9uT2JqZWN0Ij4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IklEIiBUeXBlTmFtZT0ib3BjOkd1aWQi -IC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJBc3NldElEIiBUeXBlTmFtZT0ib3BjOlN0cmluZyIg -Lz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlN0YXJ0VGltZSIgVHlwZU5hbWU9Im9wYzpEYXRlVGlt -ZSIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9Ik5vT2ZTdGF0dXNDb21tZW50cyIgVHlwZU5hbWU9 -Im9wYzpJbnQzMiIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlN0YXR1c0NvbW1lbnRzIiBUeXBl -TmFtZT0idG5zOldvcmtPcmRlclN0YXR1c1R5cGUiIExlbmd0aEZpZWxkPSJOb09mU3RhdHVzQ29t -bWVudHMiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQo8L29wYzpUeXBlRGljdGlvbmFy -eT4= +cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iVmVjdG9yVW5pb24iIEJhc2VUeXBlPSJ1YTpVbmlvbiI+ +DQogICAgPG9wYzpGaWVsZCBOYW1lPSJYIiBUeXBlTmFtZT0ib3BjOkRvdWJsZSIgLz4NCiAgICA8 +b3BjOkZpZWxkIE5hbWU9IlkiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6Rmll +bGQgTmFtZT0iWiIgVHlwZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRU +eXBlPg0KDQogIDxvcGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmll +bGRzIiBCYXNlVHlwZT0idWE6RXh0ZW5zaW9uT2JqZWN0Ij4NCiAgICA8b3BjOkZpZWxkIE5hbWU9 +IlgiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iWSIgVHlw +ZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJaIiBUeXBlTmFtZT0i +b3BjOkRvdWJsZSIgLz4NCiAgPC9vcGM6U3RydWN0dXJlZFR5cGU+DQoNCiAgPG9wYzpTdHJ1Y3R1 +cmVkVHlwZSBOYW1lPSJNdWx0aXBsZVZlY3RvcnMiIEJhc2VUeXBlPSJ1YTpFeHRlbnNpb25PYmpl +Y3QiPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yIiBUeXBlTmFtZT0idG5zOlZlY3RvciIg +Lz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlZlY3RvclVuaW9uIiBUeXBlTmFtZT0idG5zOlZlY3Rv +clVuaW9uIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRz +IiBUeXBlTmFtZT0idG5zOlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyIgLz4NCiAgICA8b3BjOkZp +ZWxkIE5hbWU9Ik5vT2ZWZWN0b3JBcnJheSIgVHlwZU5hbWU9Im9wYzpJbnQzMiIgLz4NCiAgICA8 +b3BjOkZpZWxkIE5hbWU9IlZlY3RvckFycmF5IiBUeXBlTmFtZT0idG5zOlZlY3RvciIgTGVuZ3Ro +RmllbGQ9Ik5vT2ZWZWN0b3JBcnJheSIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9Ik5vT2ZWZWN0 +b3JVbmlvbkFycmF5IiBUeXBlTmFtZT0ib3BjOkludDMyIiAvPg0KICAgIDxvcGM6RmllbGQgTmFt +ZT0iVmVjdG9yVW5pb25BcnJheSIgVHlwZU5hbWU9InRuczpWZWN0b3JVbmlvbiIgTGVuZ3RoRmll +bGQ9Ik5vT2ZWZWN0b3JVbmlvbkFycmF5IiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iTm9PZlZl +Y3RvcldpdGhPcHRpb25hbEZpZWxkc0FycmF5IiBUeXBlTmFtZT0ib3BjOkludDMyIiAvPg0KICAg +IDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzQXJyYXkiIFR5cGVOYW1l +PSJ0bnM6VmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzIiBMZW5ndGhGaWVsZD0iTm9PZlZlY3Rvcldp +dGhPcHRpb25hbEZpZWxkc0FycmF5IiAvPg0KICA8L29wYzpTdHJ1Y3R1cmVkVHlwZT4NCg0KICA8 +b3BjOlN0cnVjdHVyZWRUeXBlIE5hbWU9IldvcmtPcmRlclN0YXR1c1R5cGUiIEJhc2VUeXBlPSJ1 +YTpFeHRlbnNpb25PYmplY3QiPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQWN0b3IiIFR5cGVOYW1l +PSJvcGM6U3RyaW5nIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVGltZXN0YW1wIiBUeXBlTmFt +ZT0ib3BjOkRhdGVUaW1lIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQ29tbWVudCIgVHlwZU5h +bWU9InVhOkxvY2FsaXplZFRleHQiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQogIDxv +cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iV29ya09yZGVyVHlwZSIgQmFzZVR5cGU9InVhOkV4dGVu +c2lvbk9iamVjdCI+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJJRCIgVHlwZU5hbWU9Im9wYzpHdWlk +IiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQXNzZXRJRCIgVHlwZU5hbWU9Im9wYzpTdHJpbmci +IC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJTdGFydFRpbWUiIFR5cGVOYW1lPSJvcGM6RGF0ZVRp +bWUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJOb09mU3RhdHVzQ29tbWVudHMiIFR5cGVOYW1l +PSJvcGM6SW50MzIiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJTdGF0dXNDb21tZW50cyIgVHlw +ZU5hbWU9InRuczpXb3JrT3JkZXJTdGF0dXNUeXBlIiBMZW5ndGhGaWVsZD0iTm9PZlN0YXR1c0Nv +bW1lbnRzIiAvPg0KICA8L29wYzpTdHJ1Y3R1cmVkVHlwZT4NCg0KPC9vcGM6VHlwZURpY3Rpb25h +cnk+ @@ -40304,6 +41078,90 @@ eT4= 1 1 + + Variable_2 + + ns=1;i=3592 + + + 1 + VectorUnion + + + i=47 + + + i=69 + + 3592 + + + VectorUnion + + + + i=12 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3595 + + + 1 + VectorWithOptionalFields + + + i=47 + + + i=69 + + 3595 + + + VectorWithOptionalFields + + + + i=12 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3619 + + + 1 + MultipleVectors + + + i=47 + + + i=69 + + 3619 + + + MultipleVectors + + + + i=12 + + -1 + 1 + 1 + Variable_2 @@ -40526,6 +41384,105 @@ eT4= + + Object_1 + + ns=1;i=3598 + + + 0 + Default XML + + + i=76 + + 3598 + + + + i=38 + + true + + ns=1;i=3584 + + + + + i=39 + + + ns=1;i=3600 + + + + + + Object_1 + + ns=1;i=3599 + + + 0 + Default XML + + + i=76 + + 3599 + + + + i=38 + + true + + ns=1;i=3585 + + + + + i=39 + + + ns=1;i=3603 + + + + + + Object_1 + + ns=1;i=3622 + + + 0 + Default XML + + + i=76 + + 3622 + + + + i=38 + + true + + ns=1;i=3615 + + + + + i=39 + + + ns=1;i=3623 + + + + Object_1 @@ -40613,7 +41570,7 @@ c2QiDQogIHhtbG5zOnRucz0iaHR0cDovL3Rlc3Qub3JnL1VBL0RhdGEvIg0KICB0YXJnZXROYW1l c3BhY2U9Imh0dHA6Ly90ZXN0Lm9yZy9VQS9EYXRhLyINCiAgZWxlbWVudEZvcm1EZWZhdWx0PSJx dWFsaWZpZWQiDQo+DQogIDx4czphbm5vdGF0aW9uPg0KICAgIDx4czphcHBpbmZvPg0KICAgICAg PHVhOk1vZGVsIE1vZGVsVXJpPSJodHRwOi8vdGVzdC5vcmcvVUEvRGF0YS8iIFZlcnNpb249IjEu -MC4wIiBQdWJsaWNhdGlvbkRhdGU9IjIwMjMtMDYtMDZUMDY6MzM6NTEuNTc2OTYyOVoiIC8+DQog +MC4wIiBQdWJsaWNhdGlvbkRhdGU9IjIwMjMtMTAtMDlUMDg6NTE6MjQuNTIzNzA4OFoiIC8+DQog ICAgPC94czphcHBpbmZvPg0KICA8L3hzOmFubm90YXRpb24+DQogIA0KICA8eHM6aW1wb3J0IG5h bWVzcGFjZT0iaHR0cDovL29wY2ZvdW5kYXRpb24ub3JnL1VBLzIwMDgvMDIvVHlwZXMueHNkIiAv Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJTY2FsYXJTdHJ1Y3R1cmVEYXRhVHlwZSI+DQog @@ -40852,35 +41809,82 @@ YW1lPSJWZWN0b3IiIHR5cGU9InRuczpWZWN0b3IiIG1pbk9jY3Vycz0iMCIgbWF4T2NjdXJzPSJ1 bmJvdW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6 Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZlZlY3RvciIgdHlwZT0idG5z Okxpc3RPZlZlY3RvciIgbmlsbGFibGU9InRydWUiPjwveHM6ZWxlbWVudD4NCg0KICA8eHM6Y29t -cGxleFR5cGUgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0K -ICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQWN0b3IiIHR5cGU9InhzOnN0cmluZyIgbWluT2NjdXJz -PSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlRpbWVzdGFt -cCIgdHlwZT0ieHM6ZGF0ZVRpbWUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4czplbGVtZW50 -IG5hbWU9IkNvbW1lbnQiIHR5cGU9InVhOkxvY2FsaXplZFRleHQiIG1pbk9jY3Vycz0iMCIgbmls -bGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4VHlwZT4N -CiAgPHhzOmVsZW1lbnQgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgdHlwZT0idG5zOldvcmtP -cmRlclN0YXR1c1R5cGUiIC8+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RPZldvcmtP -cmRlclN0YXR1c1R5cGUiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4czplbGVtZW50IG5h -bWU9IldvcmtPcmRlclN0YXR1c1R5cGUiIHR5cGU9InRuczpXb3JrT3JkZXJTdGF0dXNUeXBlIiBt -aW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAg -ICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1l -PSJMaXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiB0eXBlPSJ0bnM6TGlzdE9mV29ya09yZGVyU3Rh -dHVzVHlwZSIgbmlsbGFibGU9InRydWUiPjwveHM6ZWxlbWVudD4NCg0KICA8eHM6Y29tcGxleFR5 -cGUgbmFtZT0iV29ya09yZGVyVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVs -ZW1lbnQgbmFtZT0iSUQiIHR5cGU9InVhOkd1aWQiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4 -czplbGVtZW50IG5hbWU9IkFzc2V0SUQiIHR5cGU9InhzOnN0cmluZyIgbWluT2NjdXJzPSIwIiBu -aWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlN0YXJ0VGltZSIgdHlw -ZT0ieHM6ZGF0ZVRpbWUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9 -IlN0YXR1c0NvbW1lbnRzIiB0eXBlPSJ0bnM6TGlzdE9mV29ya09yZGVyU3RhdHVzVHlwZSIgbWlu -T2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hz -OmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3JkZXJUeXBlIiB0eXBlPSJ0 -bnM6V29ya09yZGVyVHlwZSIgLz4NCg0KICA8eHM6Y29tcGxleFR5cGUgbmFtZT0iTGlzdE9mV29y -a09yZGVyVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0i -V29ya09yZGVyVHlwZSIgdHlwZT0idG5zOldvcmtPcmRlclR5cGUiIG1pbk9jY3Vycz0iMCIgbWF4 -T2NjdXJzPSJ1bmJvdW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+ -DQogIDwveHM6Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZldvcmtPcmRl -clR5cGUiIHR5cGU9InRuczpMaXN0T2ZXb3JrT3JkZXJUeXBlIiBuaWxsYWJsZT0idHJ1ZSI+PC94 -czplbGVtZW50Pg0KDQo8L3hzOnNjaGVtYT4= +cGxleFR5cGUgbmFtZT0iVmVjdG9yVW5pb24iPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4 +czplbGVtZW50IG5hbWU9IlN3aXRjaEZpZWxkIiB0eXBlPSJ4czp1bnNpZ25lZEludCIgbWluT2Nj +dXJzPSIwIiAvPg0KICAgICAgPHhzOmNob2ljZT4NCiAgICAgICAgPHhzOmVsZW1lbnQgbmFtZT0i +WCIgdHlwZT0ieHM6ZG91YmxlIiBtaW5PY2N1cnM9IjAiIC8+DQogICAgICAgIDx4czplbGVtZW50 +IG5hbWU9IlkiIHR5cGU9InhzOmRvdWJsZSIgbWluT2NjdXJzPSIwIiAvPg0KICAgICAgICA8eHM6 +ZWxlbWVudCBuYW1lPSJaIiB0eXBlPSJ4czpkb3VibGUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAg +IDwveHM6Y2hvaWNlPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29tcGxleFR5cGU+DQog +IDx4czplbGVtZW50IG5hbWU9IlZlY3RvclVuaW9uIiB0eXBlPSJ0bnM6VmVjdG9yVW5pb24iIC8+ +DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RPZlZlY3RvclVuaW9uIj4NCiAgICA8eHM6 +c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJWZWN0b3JVbmlvbiIgdHlwZT0idG5z +OlZlY3RvclVuaW9uIiBtaW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBuaWxsYWJs +ZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8 +eHM6ZWxlbWVudCBuYW1lPSJMaXN0T2ZWZWN0b3JVbmlvbiIgdHlwZT0idG5zOkxpc3RPZlZlY3Rv +clVuaW9uIiBuaWxsYWJsZT0idHJ1ZSI+PC94czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlw +ZSBuYW1lPSJWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAg +ICAgIDx4czplbGVtZW50IG5hbWU9IlgiIHR5cGU9InhzOmRvdWJsZSIgbWluT2NjdXJzPSIwIiAv +Pg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iWSIgdHlwZT0ieHM6ZG91YmxlIiBtaW5PY2N1cnM9 +IjAiIC8+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJaIiB0eXBlPSJ4czpkb3VibGUiIG1pbk9j +Y3Vycz0iMCIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8 +eHM6ZWxlbWVudCBuYW1lPSJWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIHR5cGU9InRuczpWZWN0 +b3JXaXRoT3B0aW9uYWxGaWVsZHMiIC8+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RP +ZlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzIiB0eXBlPSJ0bnM6VmVjdG9y +V2l0aE9wdGlvbmFsRmllbGRzIiBtaW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBu +aWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBl +Pg0KICA8eHM6ZWxlbWVudCBuYW1lPSJMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIHR5 +cGU9InRuczpMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIG5pbGxhYmxlPSJ0cnVlIj48 +L3hzOmVsZW1lbnQ+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ik11bHRpcGxlVmVjdG9ycyI+ +DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yIiB0eXBl +PSJ0bnM6VmVjdG9yIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iVmVjdG9yVW5pb24iIHR5cGU9InRuczpWZWN0b3JVbmlvbiIgbWluT2Nj +dXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlZlY3Rv +cldpdGhPcHRpb25hbEZpZWxkcyIgdHlwZT0idG5zOlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyIg +bWluT2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9 +IlZlY3RvckFycmF5IiB0eXBlPSJ0bnM6TGlzdE9mVmVjdG9yIiBtaW5PY2N1cnM9IjAiIG5pbGxh +YmxlPSJ0cnVlIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yVW5pb25BcnJheSIg +dHlwZT0idG5zOkxpc3RPZlZlY3RvclVuaW9uIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVl +IiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzQXJy +YXkiIHR5cGU9InRuczpMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIG1pbk9jY3Vycz0i +MCIgbmlsbGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4 +VHlwZT4NCiAgPHhzOmVsZW1lbnQgbmFtZT0iTXVsdGlwbGVWZWN0b3JzIiB0eXBlPSJ0bnM6TXVs +dGlwbGVWZWN0b3JzIiAvPg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJMaXN0T2ZNdWx0aXBs +ZVZlY3RvcnMiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9Ik11 +bHRpcGxlVmVjdG9ycyIgdHlwZT0idG5zOk11bHRpcGxlVmVjdG9ycyIgbWluT2NjdXJzPSIwIiBt +YXhPY2N1cnM9InVuYm91bmRlZCIgbmlsbGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5j +ZT4NCiAgPC94czpjb21wbGV4VHlwZT4NCiAgPHhzOmVsZW1lbnQgbmFtZT0iTGlzdE9mTXVsdGlw +bGVWZWN0b3JzIiB0eXBlPSJ0bnM6TGlzdE9mTXVsdGlwbGVWZWN0b3JzIiBuaWxsYWJsZT0idHJ1 +ZSI+PC94czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJXb3JrT3JkZXJTdGF0 +dXNUeXBlIj4NCiAgICA8eHM6c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJBY3Rv +ciIgdHlwZT0ieHM6c3RyaW5nIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAg +ICAgPHhzOmVsZW1lbnQgbmFtZT0iVGltZXN0YW1wIiB0eXBlPSJ4czpkYXRlVGltZSIgbWluT2Nj +dXJzPSIwIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQ29tbWVudCIgdHlwZT0idWE6TG9j +YWxpemVkVGV4dCIgbWluT2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNl +cXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3Jk +ZXJTdGF0dXNUeXBlIiB0eXBlPSJ0bnM6V29ya09yZGVyU3RhdHVzVHlwZSIgLz4NCg0KICA8eHM6 +Y29tcGxleFR5cGUgbmFtZT0iTGlzdE9mV29ya09yZGVyU3RhdHVzVHlwZSI+DQogICAgPHhzOnNl +cXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgdHlw +ZT0idG5zOldvcmtPcmRlclN0YXR1c1R5cGUiIG1pbk9jY3Vycz0iMCIgbWF4T2NjdXJzPSJ1bmJv +dW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29t +cGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZldvcmtPcmRlclN0YXR1c1R5cGUi +IHR5cGU9InRuczpMaXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiBuaWxsYWJsZT0idHJ1ZSI+PC94 +czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJXb3JrT3JkZXJUeXBlIj4NCiAg +ICA8eHM6c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJJRCIgdHlwZT0idWE6R3Vp +ZCIgbWluT2NjdXJzPSIwIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQXNzZXRJRCIgdHlw +ZT0ieHM6c3RyaW5nIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iU3RhcnRUaW1lIiB0eXBlPSJ4czpkYXRlVGltZSIgbWluT2NjdXJzPSIw +IiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iU3RhdHVzQ29tbWVudHMiIHR5cGU9InRuczpM +aXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAv +Pg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50 +IG5hbWU9IldvcmtPcmRlclR5cGUiIHR5cGU9InRuczpXb3JrT3JkZXJUeXBlIiAvPg0KDQogIDx4 +czpjb21wbGV4VHlwZSBuYW1lPSJMaXN0T2ZXb3JrT3JkZXJUeXBlIj4NCiAgICA8eHM6c2VxdWVu +Y2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3JkZXJUeXBlIiB0eXBlPSJ0bnM6V29y +a09yZGVyVHlwZSIgbWluT2NjdXJzPSIwIiBtYXhPY2N1cnM9InVuYm91bmRlZCIgbmlsbGFibGU9 +InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4VHlwZT4NCiAgPHhz +OmVsZW1lbnQgbmFtZT0iTGlzdE9mV29ya09yZGVyVHlwZSIgdHlwZT0idG5zOkxpc3RPZldvcmtP +cmRlclR5cGUiIG5pbGxhYmxlPSJ0cnVlIj48L3hzOmVsZW1lbnQ+DQoNCjwveHM6c2NoZW1hPg== @@ -41096,6 +42100,90 @@ czplbGVtZW50Pg0KDQo8L3hzOnNjaGVtYT4= 1 1 + + Variable_2 + + ns=1;i=3600 + + + 1 + VectorUnion + + + i=47 + + + i=69 + + 3600 + + + //xs:element[@name='VectorUnion'] + + + + i=12 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3603 + + + 1 + VectorWithOptionalFields + + + i=47 + + + i=69 + + 3603 + + + //xs:element[@name='VectorWithOptionalFields'] + + + + i=12 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3623 + + + 1 + MultipleVectors + + + i=47 + + + i=69 + + 3623 + + + //xs:element[@name='MultipleVectors'] + + + + i=12 + + -1 + 1 + 1 + Variable_2 @@ -41278,6 +42366,81 @@ czplbGVtZW50Pg0KDQo8L3hzOnNjaGVtYT4= + + Object_1 + + ns=1;i=3606 + + + 0 + Default JSON + + + i=76 + + 3606 + + + + i=38 + + true + + ns=1;i=3584 + + + + + + Object_1 + + ns=1;i=3607 + + + 0 + Default JSON + + + i=76 + + 3607 + + + + i=38 + + true + + ns=1;i=3585 + + + + + + Object_1 + + ns=1;i=3626 + + + 0 + Default JSON + + + i=76 + + 3626 + + + + i=38 + + true + + ns=1;i=3615 + + + + Object_1 diff --git a/Applications/Quickstarts.Servers/TestData/TestData.Types.bsd b/Applications/Quickstarts.Servers/TestData/TestData.Types.bsd index a2fc611e2..9b0f82f19 100644 --- a/Applications/Quickstarts.Servers/TestData/TestData.Types.bsd +++ b/Applications/Quickstarts.Servers/TestData/TestData.Types.bsd @@ -239,6 +239,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Applications/Quickstarts.Servers/TestData/TestData.Types.xsd b/Applications/Quickstarts.Servers/TestData/TestData.Types.xsd index 5d6b17cf8..abf2afc54 100644 --- a/Applications/Quickstarts.Servers/TestData/TestData.Types.xsd +++ b/Applications/Quickstarts.Servers/TestData/TestData.Types.xsd @@ -7,7 +7,7 @@ > - + @@ -235,6 +235,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Applications/Quickstarts.Servers/TestData/TestDataDesign.csv b/Applications/Quickstarts.Servers/TestData/TestDataDesign.csv index d61bd615a..f21636eff 100644 --- a/Applications/Quickstarts.Servers/TestData/TestDataDesign.csv +++ b/Applications/Quickstarts.Servers/TestData/TestDataDesign.csv @@ -2579,3 +2579,51 @@ UserArrayValueDataType_Encoding_DefaultJson,3578,Object Vector_Encoding_DefaultJson,3579,Object WorkOrderStatusType_Encoding_DefaultJson,3580,Object WorkOrderType_Encoding_DefaultJson,3581,Object +ScalarValueObjectType_VectorUnionValue,3582,Variable +ScalarValueObjectType_VectorWithOptionalFieldsValue,3583,Variable +VectorUnion,3584,DataType +VectorWithOptionalFields,3585,DataType +Data_Static_Scalar_VectorUnionValue,3586,Variable +Data_Static_Scalar_VectorWithOptionalFieldsValue,3587,Variable +Data_Dynamic_Scalar_VectorUnionValue,3588,Variable +Data_Dynamic_Scalar_VectorWithOptionalFieldsValue,3589,Variable +VectorUnion_Encoding_DefaultBinary,3590,Object +VectorWithOptionalFields_Encoding_DefaultBinary,3591,Object +TestData_BinarySchema_VectorUnion,3592,Variable +TestData_BinarySchema_VectorUnion_DataTypeVersion,3593,Variable +TestData_BinarySchema_VectorUnion_DictionaryFragment,3594,Variable +TestData_BinarySchema_VectorWithOptionalFields,3595,Variable +TestData_BinarySchema_VectorWithOptionalFields_DataTypeVersion,3596,Variable +TestData_BinarySchema_VectorWithOptionalFields_DictionaryFragment,3597,Variable +VectorUnion_Encoding_DefaultXml,3598,Object +VectorWithOptionalFields_Encoding_DefaultXml,3599,Object +TestData_XmlSchema_VectorUnion,3600,Variable +TestData_XmlSchema_VectorUnion_DataTypeVersion,3601,Variable +TestData_XmlSchema_VectorUnion_DictionaryFragment,3602,Variable +TestData_XmlSchema_VectorWithOptionalFields,3603,Variable +TestData_XmlSchema_VectorWithOptionalFields_DataTypeVersion,3604,Variable +TestData_XmlSchema_VectorWithOptionalFields_DictionaryFragment,3605,Variable +VectorUnion_Encoding_DefaultJson,3606,Object +VectorWithOptionalFields_Encoding_DefaultJson,3607,Object +ArrayValueObjectType_VectorUnionValue,3608,Variable +ArrayValueObjectType_VectorWithOptionalFieldsValue,3609,Variable +Data_Static_Array_VectorUnionValue,3610,Variable +Data_Static_Array_VectorWithOptionalFieldsValue,3611,Variable +Data_Dynamic_Array_VectorUnionValue,3612,Variable +Data_Dynamic_Array_VectorWithOptionalFieldsValue,3613,Variable +ScalarValueObjectType_MultipleVectorsValue,3614,Variable +MultipleVectors,3615,DataType +Data_Static_Scalar_MultipleVectorsValue,3616,Variable +Data_Dynamic_Scalar_MultipleVectorsValue,3617,Variable +MultipleVectors_Encoding_DefaultBinary,3618,Object +TestData_BinarySchema_MultipleVectors,3619,Variable +TestData_BinarySchema_MultipleVectors_DataTypeVersion,3620,Variable +TestData_BinarySchema_MultipleVectors_DictionaryFragment,3621,Variable +MultipleVectors_Encoding_DefaultXml,3622,Object +TestData_XmlSchema_MultipleVectors,3623,Variable +TestData_XmlSchema_MultipleVectors_DataTypeVersion,3624,Variable +TestData_XmlSchema_MultipleVectors_DictionaryFragment,3625,Variable +MultipleVectors_Encoding_DefaultJson,3626,Object +ArrayValueObjectType_MultipleVectorsValue,3627,Variable +Data_Static_Array_MultipleVectorsValue,3628,Variable +Data_Dynamic_Array_MultipleVectorsValue,3629,Variable diff --git a/Applications/Quickstarts.Servers/TestData/TestDataDesign.xml b/Applications/Quickstarts.Servers/TestData/TestDataDesign.xml index ed59e1d03..aa85572dd 100644 --- a/Applications/Quickstarts.Servers/TestData/TestDataDesign.xml +++ b/Applications/Quickstarts.Servers/TestData/TestDataDesign.xml @@ -258,6 +258,9 @@ + + + @@ -417,6 +420,9 @@ + + + @@ -643,7 +649,7 @@ - spec V1.03 released in 2015 +- 1.4.x.x --> spec V1.04 released in 2017 +- 1.5.x.x --> spec V1.05 released in 2022 + +The OPC UA spec is committed to backward compatibility, so the 1.04 releases work also with 1.03 certified servers and clients. +Once the UA working group releases the spec V1.05.03, the Nuget packages will be updated to be based on a 1.05 Nodeset (ETA Q1 2024), which can still be used to certify a V1.04 UA application. + +The next digits in the Nuget package version represent the API level and the build number. +An API level is mapped to a dedicated branch for a release, e.g. [release/1.4.372](https://github.com/OPCFoundation/UA-.NETStandard/tree/release/1.4.372). +Thus for hotfixes, a released API level can easily receive cherry picks or security updates from the main branch. +An API level remains in itself consistent, that it should not receive breaking changes that would require code changes in applications. +However, internal improvements or even small features which extend existing APIs that may not require application changes may be included in build updates. +In fact the versioning doesn't map to the MAJOR.MINOR.PATCH semantic versioning. The build number corresponds to a mix of MINOR and PATCH, the API level corresponds to MAJOR (breaking changes). The spec version prefix however is guaranteed to be downwards compatible, and it should be possible to certify a UA Server that is built with a 1.5 library with a 1.4 certification test. + +Currently the released Nuget packages support a wide variety of .NET platforms: + +The following .NET versions are currently supported by the class libraries +- .NET Standard 2.0 +- .NET Standard 2.1 +- .NET Framework 4.8 * +- .NET 6.0 * +- .NET 8.0 ** + +The following platform is deprecated but can still be built and tested: +- .NET Framework 4.6.2 + +To reduce the ci build overhead and the number of tests to be run in Visual Studio, only the tagged versions (* and **) are part of a qualifying ci build to pass a pull request. +All other platforms are only tested in weekly scheduled or manual ci builds. + +By default, in Visual Studio only the platforms tagged with (*) are tested. In order to test the other platforms in a command line window or in VS, there is a custom build variable defined to target a specific build. E.g. to target a .NETStandard2.0 build, the test runners are compiled with .NET 6.0 but the class libraries target only netstandard2.0, to force the use of that target. +Another option is to test run such a custom target in a command window with a batch file [CustomTest.bat](../Tests/customtest.bat) which is provided to clean up, restore the project and to run the tests. To run the custom tests in Visual Studio a section in [target.props](../targets.props) needs to be uncommented and the target platform value must be set. + +```xml + + + + netstandard2.0 + +``` + +Due to the limitations of the build system it is recommended to run the CustomTest batch file as well to force a clean build of the project for the test target. + + +## Further information on the supported Nuget packages + +The OPCFoundation prefix is reserved and the assemblies and the Nuget packages are signed by the OPC Foundation. + +For improved source level debugging in Visual Studio, symbol packages are available on Nuget.org in the 'snupkg' format. A reference to the Nuget symbol server may have to be added in Visual Studio to enable the source level debug support. +In addition packages compiled as Debug are available on Nuget.org with a '.Debug' extension to the package name. + +[OPCFoundation.NetStandard.Opc.Ua](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua/) + +This is a wrapper package to include all the available packages from this repository, except PubSub. It is recommended to rather include the individual packages as below to reduce the number of dependencies. + +[OPCFoundation.NetStandard.Opc.Ua.Core](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Core/) +[OPCFoundation.NetStandard.Opc.Ua.Security.Certificates](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Security.Certificates/) + +Core and Certificates are required for Client and Server projects. + +[OPCFoundation.NetStandard.Opc.Ua.Configuration](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Configuration/) + +The configuration contains a helper class to configure a UA application from file or with a fluent API. + +[OPCFoundation.NetStandard.Opc.Ua.Server](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Server/) + +The server library is used to build a UA server. + +[OPCFoundation.NetStandard.Opc.Ua.Client](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Client/) +[OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes/) + +The client is used to build a client. The complex type library extends the client with support for complex types. + +[OPCFoundation.NetStandard.Opc.Ua.Bindings.Https](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Bindings.Https/) + +The Https binding is an optional component to support UA over Https for 'opc.https' endpoints. + +[OPCFoundation.NetStandard.Opc.Ua.PubSub](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.PubSub/) (Beta) + +The PubSub library can be used to implement the publisher subscriber model. + +For the licenses please see the information in the packages. diff --git a/Docs/README.md b/Docs/README.md index 0630e1901..1f57280a1 100644 --- a/Docs/README.md +++ b/Docs/README.md @@ -5,6 +5,7 @@ Here is a list of available documentation for different topics: UA Core stack related: +* About [.NET platform](PlatformBuild.md) support, Nuget packages and versioning. * How X.509 [Certificates](Certificates.md) are used in the certificate stores. * Using the [Reverse Connect](ReverseConnect.md) for the UA-TCP transport. * Support for the [TransferSubscriptions](TransferSubscription.md) service set. diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystem.cs b/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystem.cs index b685528d1..dc3d64c85 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystem.cs +++ b/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystem.cs @@ -29,8 +29,8 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Xml; using static Opc.Ua.Utils; @@ -114,14 +114,14 @@ private void Initialize( /// For servers without DataTypeDefinition support, all /// custom types are loaded. /// - public async Task LoadType(ExpandedNodeId nodeId, bool subTypes = false, bool throwOnError = false) + public async Task LoadType(ExpandedNodeId nodeId, bool subTypes = false, bool throwOnError = false, CancellationToken ct = default) { try { // add fast path, if no subTypes are requested if (!subTypes) { - var systemType = GetSystemType(nodeId); + Type systemType = GetSystemType(nodeId); if (systemType != null) { return systemType; @@ -129,25 +129,25 @@ public async Task LoadType(ExpandedNodeId nodeId, bool subTypes = false, b } // cache the server type system - m_complexTypeResolver.LoadDataTypes(DataTypeIds.BaseDataType, true); - var subTypeNodes = m_complexTypeResolver.LoadDataTypes(nodeId, subTypes, true); - var subTypeNodesWithoutKnownTypes = RemoveKnownTypes(subTypeNodes); + _ = await m_complexTypeResolver.LoadDataTypesAsync(DataTypeIds.BaseDataType, true, ct: ct).ConfigureAwait(false); + IList subTypeNodes = await m_complexTypeResolver.LoadDataTypesAsync(nodeId, subTypes, true, ct: ct).ConfigureAwait(false); + IList subTypeNodesWithoutKnownTypes = RemoveKnownTypes(subTypeNodes); if (subTypeNodesWithoutKnownTypes.Count > 0) { IList serverEnumTypes = new List(); IList serverStructTypes = new List(); - foreach (var node in subTypeNodesWithoutKnownTypes) + foreach (INode node in subTypeNodesWithoutKnownTypes) { - AddEnumerationOrStructureType(node, serverEnumTypes, serverStructTypes); + await AddEnumerationOrStructureTypeAsync(node, serverEnumTypes, serverStructTypes, ct).ConfigureAwait(false); } // load server types - if (DisableDataTypeDefinition || !LoadBaseDataTypes(serverEnumTypes, serverStructTypes)) + if (DisableDataTypeDefinition || !await LoadBaseDataTypesAsync(serverEnumTypes, serverStructTypes, ct).ConfigureAwait(false)) { if (!DisableDataTypeDictionary) { - await LoadDictionaryDataTypes(serverEnumTypes, serverStructTypes, false).ConfigureAwait(false); + await LoadDictionaryDataTypes(serverEnumTypes, serverStructTypes, false, ct).ConfigureAwait(false); } } } @@ -173,7 +173,7 @@ public async Task LoadType(ExpandedNodeId nodeId, bool subTypes = false, b /// For servers without DataTypeDefinition support all /// custom types are loaded. /// - public async Task LoadNamespace(string nameSpace, bool throwOnError = false) + public async Task LoadNamespace(string nameSpace, bool throwOnError = false, CancellationToken ct = default) { try { @@ -183,20 +183,20 @@ public async Task LoadNamespace(string nameSpace, bool throwOnError = fals throw new ServiceResultException($"Bad argument {nameSpace}. Namespace not found."); } ushort nameSpaceIndex = (ushort)index; - m_complexTypeResolver.LoadDataTypes(DataTypeIds.BaseDataType, true); - var serverEnumTypes = m_complexTypeResolver.LoadDataTypes(DataTypeIds.Enumeration); - var serverStructTypes = m_complexTypeResolver.LoadDataTypes(DataTypeIds.Structure, true); + _ = await m_complexTypeResolver.LoadDataTypesAsync(DataTypeIds.BaseDataType, true, ct: ct).ConfigureAwait(false); + IList serverEnumTypes = await m_complexTypeResolver.LoadDataTypesAsync(DataTypeIds.Enumeration, ct: ct).ConfigureAwait(false); + IList serverStructTypes = await m_complexTypeResolver.LoadDataTypesAsync(DataTypeIds.Structure, true, ct: ct).ConfigureAwait(false); // filter for namespace serverEnumTypes = serverEnumTypes.Where(rd => rd.NodeId.NamespaceIndex == nameSpaceIndex).ToList(); serverStructTypes = serverStructTypes.Where(rd => rd.NodeId.NamespaceIndex == nameSpaceIndex).ToList(); // load types - if (DisableDataTypeDefinition || !LoadBaseDataTypes(serverEnumTypes, serverStructTypes)) + if (DisableDataTypeDefinition || !await LoadBaseDataTypesAsync(serverEnumTypes, serverStructTypes, ct).ConfigureAwait(false)) { if (DisableDataTypeDictionary) { return false; } - return await LoadDictionaryDataTypes(serverEnumTypes, serverStructTypes, false).ConfigureAwait(false); + return await LoadDictionaryDataTypes(serverEnumTypes, serverStructTypes, false, ct).ConfigureAwait(false); } return true; } @@ -228,21 +228,22 @@ public async Task LoadNamespace(string nameSpace, bool throwOnError = fals /// - Create all structured types from the dictionaries using the converted DataTypeDefinion attribute.. /// /// true if all DataTypes were loaded. - public async Task Load(bool onlyEnumTypes = false, bool throwOnError = false) + public async Task Load(bool onlyEnumTypes = false, bool throwOnError = false, CancellationToken ct = default) { try { // load server types in cache - m_complexTypeResolver.LoadDataTypes(DataTypeIds.BaseDataType, true); - IList serverEnumTypes = m_complexTypeResolver.LoadDataTypes(DataTypeIds.Enumeration); - IList serverStructTypes = onlyEnumTypes ? new List() : m_complexTypeResolver.LoadDataTypes(DataTypeIds.Structure, true); - if (DisableDataTypeDefinition || !LoadBaseDataTypes(serverEnumTypes, serverStructTypes)) + await m_complexTypeResolver.LoadDataTypesAsync(DataTypeIds.BaseDataType, true, ct: ct).ConfigureAwait(false); + IList serverEnumTypes = await m_complexTypeResolver.LoadDataTypesAsync(DataTypeIds.Enumeration, ct: ct).ConfigureAwait(false); + IList serverStructTypes = onlyEnumTypes ? new List() : + await m_complexTypeResolver.LoadDataTypesAsync(DataTypeIds.Structure, true, ct: ct).ConfigureAwait(false); + if (DisableDataTypeDefinition || !await LoadBaseDataTypesAsync(serverEnumTypes, serverStructTypes, ct).ConfigureAwait(false)) { if (DisableDataTypeDictionary) { return false; } - return await LoadDictionaryDataTypes(serverEnumTypes, serverStructTypes, true).ConfigureAwait(false); + return await LoadDictionaryDataTypes(serverEnumTypes, serverStructTypes, true, ct).ConfigureAwait(false); } return true; } @@ -298,7 +299,7 @@ void CollectAllDataTypeDefinitions(NodeId nodeId, NodeIdDictionary + /// Clear references in datatype cache. + /// + public void ClearDataTypeCache() + { + m_dataTypeDefinitionCache.Clear(); + } #endregion Public Members #region Internal Properties @@ -343,18 +351,19 @@ void CollectAllDataTypeDefinitions(NodeId nodeId, NodeIdDictionary LoadDictionaryDataTypes( IList serverEnumTypes, IList serverStructTypes, - bool fullTypeList + bool fullTypeList, + CancellationToken ct = default ) { // build a type dictionary with all known new types - var allEnumTypes = fullTypeList ? serverEnumTypes : m_complexTypeResolver.LoadDataTypes(DataTypeIds.Enumeration); + var allEnumTypes = fullTypeList ? serverEnumTypes : await m_complexTypeResolver.LoadDataTypesAsync(DataTypeIds.Enumeration, ct: ct).ConfigureAwait(false); var typeDictionary = new Dictionary(); // strip known types from list serverEnumTypes = RemoveKnownTypes(allEnumTypes); // load the binary schema dictionaries from the server - var typeSystem = await m_complexTypeResolver.LoadDataTypeSystem().ConfigureAwait(false); + Dictionary typeSystem = await m_complexTypeResolver.LoadDataTypeSystem(ct: ct).ConfigureAwait(false); // sort dictionaries with import dependencies to the end of the list var sortedTypeSystem = typeSystem.OrderBy(t => t.Value.TypeDictionary?.Import?.Length).ToList(); @@ -362,18 +371,18 @@ bool fullTypeList bool allTypesLoaded = true; // create custom types for all dictionaries - foreach (var dictionaryId in sortedTypeSystem) + foreach (KeyValuePair dictionaryId in sortedTypeSystem) { try { - var dictionary = dictionaryId.Value; + DataDictionary dictionary = dictionaryId.Value; if (dictionary.TypeDictionary == null || dictionary.TypeDictionary.Items == null) { continue; } - var targetDictionaryNamespace = dictionary.TypeDictionary.TargetNamespace; - var targetNamespaceIndex = m_complexTypeResolver.NamespaceUris.GetIndex(targetDictionaryNamespace); + string targetDictionaryNamespace = dictionary.TypeDictionary.TargetNamespace; + int targetNamespaceIndex = m_complexTypeResolver.NamespaceUris.GetIndex(targetDictionaryNamespace); var structureList = new List(); var enumList = new List(); @@ -382,13 +391,13 @@ bool fullTypeList SplitAndSortDictionary(dictionary, structureList, enumList); // create assembly for all types in the same module - var complexTypeBuilder = m_complexTypeBuilderFactory.Create( + IComplexTypeBuilder complexTypeBuilder = m_complexTypeBuilderFactory.Create( targetDictionaryNamespace, targetNamespaceIndex, dictionary.Name); // Add all unknown enumeration types in dictionary - AddEnumTypes(complexTypeBuilder, typeDictionary, enumList, allEnumTypes, serverEnumTypes); + await AddEnumTypesAsync(complexTypeBuilder, typeDictionary, enumList, allEnumTypes, serverEnumTypes, ct).ConfigureAwait(false); // handle structures int loopCounter = 0; @@ -401,7 +410,7 @@ bool fullTypeList lastStructureCount = structureList.Count; var retryStructureList = new List(); // build structured types - foreach (var item in structureList) + foreach (Schema.Binary.TypeDescription item in structureList) { if (item is Schema.Binary.StructuredType structuredObject) { @@ -413,16 +422,10 @@ bool fullTypeList } // find the data type node and the binary encoding id - ExpandedNodeId typeId; - ExpandedNodeId binaryEncodingId; - DataTypeNode dataTypeNode; - bool newTypeDescription = m_complexTypeResolver.BrowseTypeIdsForDictionaryComponent( - nodeId, - out typeId, - out binaryEncodingId, - out dataTypeNode); - - if (!newTypeDescription) + (ExpandedNodeId typeId, ExpandedNodeId binaryEncodingId, DataTypeNode dataTypeNode) = + await m_complexTypeResolver.BrowseTypeIdsForDictionaryComponentAsync(nodeId, ct).ConfigureAwait(false); + + if (dataTypeNode == null) { Utils.LogError(TraceMasks.Error, "Skip the type definition of {0} because the data type node was not found.", item.Name); continue; @@ -430,7 +433,7 @@ bool fullTypeList if (GetSystemType(typeId) != null) { - var qName = structuredObject.QName ?? new XmlQualifiedName(structuredObject.Name, targetDictionaryNamespace); + XmlQualifiedName qName = structuredObject.QName ?? new XmlQualifiedName(structuredObject.Name, targetDictionaryNamespace); typeDictionary[qName] = ExpandedNodeId.ToNodeId(typeId, m_complexTypeResolver.NamespaceUris); Utils.LogInfo("Skip the type definition of {0} because the type already exists.", item.Name); continue; @@ -469,20 +472,22 @@ bool fullTypeList Type complexType = null; if (structureDefinition != null) { - var encodingIds = m_complexTypeResolver.BrowseForEncodings(typeId, m_supportedEncodings, - out binaryEncodingId, out ExpandedNodeId xmlEncodingId); + IList encodingIds; + ExpandedNodeId xmlEncodingId; + (encodingIds, binaryEncodingId, xmlEncodingId) = await m_complexTypeResolver.BrowseForEncodingsAsync( + typeId, m_supportedEncodings, ct).ConfigureAwait(false); try { // build the actual .NET structured type in assembly - complexType = AddStructuredType( + (complexType, missingTypeIds) = await AddStructuredTypeAsync( complexTypeBuilder, structureDefinition, dataTypeNode.BrowseName, typeId, binaryEncodingId, xmlEncodingId, - out missingTypeIds - ); + ct + ).ConfigureAwait(false); } catch (DataTypeNotSupportedException typeNotSupportedException) { @@ -500,7 +505,7 @@ out missingTypeIds AddEncodeableType(encodingId, complexType); } AddEncodeableType(typeId, complexType); - var qName = structuredObject.QName ?? new XmlQualifiedName(structuredObject.Name, targetDictionaryNamespace); + XmlQualifiedName qName = structuredObject.QName ?? new XmlQualifiedName(structuredObject.Name, targetDictionaryNamespace); typeDictionary[qName] = ExpandedNodeId.ToNodeId(typeId, m_complexTypeResolver.NamespaceUris); } } @@ -529,15 +534,16 @@ out missingTypeIds /// Load all custom types with DataTypeDefinition into the type factory. /// /// true if all types were loaded, false otherwise - private bool LoadBaseDataTypes( + private async Task LoadBaseDataTypesAsync( IList serverEnumTypes, - IList serverStructTypes + IList serverStructTypes, + CancellationToken ct = default ) { - bool repeatDataTypeLoad = false; IList enumTypesToDoList = new List(); IList structTypesToDoList = new List(); + bool repeatDataTypeLoad; do { // strip known types @@ -547,19 +553,19 @@ IList serverStructTypes repeatDataTypeLoad = false; try { - enumTypesToDoList = LoadBaseEnumDataTypes(serverEnumTypes); - structTypesToDoList = LoadBaseStructureDataTypes(serverStructTypes); + enumTypesToDoList = await LoadBaseEnumDataTypesAsync(serverEnumTypes, ct).ConfigureAwait(false); + structTypesToDoList = await LoadBaseStructureDataTypesAsync(serverStructTypes, ct).ConfigureAwait(false); } catch (DataTypeNotFoundException dtnfex) { Utils.LogWarning(dtnfex.Message); - foreach (var nodeId in dtnfex.NodeIds) + foreach (ExpandedNodeId nodeId in dtnfex.NodeIds) { // add missing types to list - var dataTypeNode = m_complexTypeResolver.Find(nodeId); + var dataTypeNode = await m_complexTypeResolver.FindAsync(nodeId, ct).ConfigureAwait(false); if (dataTypeNode != null) { - AddEnumerationOrStructureType(dataTypeNode, serverEnumTypes, serverStructTypes); + await AddEnumerationOrStructureTypeAsync(dataTypeNode, serverEnumTypes, serverStructTypes, ct).ConfigureAwait(false); repeatDataTypeLoad = true; } else @@ -578,8 +584,9 @@ IList serverStructTypes /// Load all custom types with DataTypeDefinition into the type factory. /// /// true if all types were loaded, false otherwise - private IList LoadBaseEnumDataTypes( - IList serverEnumTypes + private async Task> LoadBaseEnumDataTypesAsync( + IList serverEnumTypes, + CancellationToken ct = default ) { // strip known types @@ -603,9 +610,9 @@ IList serverEnumTypes targetNamespace, (int)i); } - foreach (var enumType in enumTypes) + foreach (INode enumType in enumTypes) { - var newType = AddEnumType(complexTypeBuilder, enumType as DataTypeNode); + Type newType = await AddEnumTypeAsync(complexTypeBuilder, enumType as DataTypeNode, ct).ConfigureAwait(false); if (newType != null) { // match namespace and add to type factory @@ -627,8 +634,9 @@ IList serverEnumTypes /// Load all structure custom types with DataTypeDefinition into the type factory. /// /// true if all types were loaded, false otherwise - private IList LoadBaseStructureDataTypes( - IList serverStructTypes + private async Task> LoadBaseStructureDataTypesAsync( + IList serverStructTypes, + CancellationToken ct = default ) { // strip known types @@ -639,11 +647,11 @@ IList serverStructTypes bool retryAddStructType; var structTypesToDoList = new List(); - var structTypesWorkList = serverStructTypes; + IList structTypesWorkList = serverStructTypes; // allow the loader to cache the encodings IList nodeIds = serverStructTypes.Select(n => n.NodeId).ToList(); - m_complexTypeResolver.BrowseForEncodings(nodeIds, m_supportedEncodings); + _ = await m_complexTypeResolver.BrowseForEncodingsAsync(nodeIds, m_supportedEncodings, ct).ConfigureAwait(false); // create structured types for all namespaces int loopCounter = 0; @@ -673,40 +681,41 @@ IList serverStructTypes continue; } - var structureDefinition = GetStructureDefinition(dataTypeNode); + StructureDefinition structureDefinition = GetStructureDefinition(dataTypeNode); if (structureDefinition != null) { - var encodingIds = m_complexTypeResolver.BrowseForEncodings(structType.NodeId, m_supportedEncodings, - out ExpandedNodeId binaryEncodingId, out ExpandedNodeId xmlEncodingId); + (IList encodingIds, ExpandedNodeId binaryEncodingId, ExpandedNodeId xmlEncodingId) + = await m_complexTypeResolver.BrowseForEncodingsAsync(structType.NodeId, m_supportedEncodings, ct).ConfigureAwait(false); try { ExpandedNodeId typeId = NormalizeExpandedNodeId(structType.NodeId); - newType = AddStructuredType( + ExpandedNodeIdCollection missingTypeIds; + (newType, missingTypeIds) = await AddStructuredTypeAsync( complexTypeBuilder, structureDefinition, dataTypeNode.BrowseName, typeId, binaryEncodingId, xmlEncodingId, - out ExpandedNodeIdCollection missingTypeIds - ); + ct + ).ConfigureAwait(false); if (missingTypeIds?.Count > 0) { var missingTypeIdsFromWorkList = new ExpandedNodeIdCollection(); - foreach (var missingTypeId in missingTypeIds) + foreach (ExpandedNodeId missingTypeId in missingTypeIds) { - var typeMatch = structTypesWorkList.FirstOrDefault(n => n.NodeId == missingTypeId); + INode typeMatch = structTypesWorkList.FirstOrDefault(n => n.NodeId == missingTypeId); if (typeMatch == null) { missingTypeIdsFromWorkList.Add(missingTypeId); } } - foreach (var id in missingTypeIdsFromWorkList) + foreach (ExpandedNodeId id in missingTypeIdsFromWorkList) { if (!structTypesToDoList.Where(n => n.NodeId == id).Any()) { - structTypesToDoList.Add(m_complexTypeResolver.Find(id)); + structTypesToDoList.Add(await m_complexTypeResolver.FindAsync(id, ct).ConfigureAwait(false)); } retryAddStructType = true; } @@ -724,7 +733,7 @@ out ExpandedNodeIdCollection missingTypeIds if (newType != null) { - foreach (var encodingId in encodingIds) + foreach (NodeId encodingId in encodingIds) { AddEncodeableType(encodingId, newType); } @@ -778,7 +787,7 @@ private StructureDefinition GetStructureDefinition(DataTypeNode dataTypeNode) return null; } // Validate the structure according to Part3, Table 36 - foreach (var field in structureDefinition.Fields) + foreach (StructureField field in structureDefinition.Fields) { // validate if the DataTypeDefinition is correctly // filled out, some servers don't do it yet... @@ -814,12 +823,12 @@ private ExpandedNodeId NormalizeExpandedNodeId(ExpandedNodeId expandedNodeId) /// /// Add data type to enumeration or structure base type list depending on supertype. /// - private void AddEnumerationOrStructureType(INode dataTypeNode, IList serverEnumTypes, IList serverStructTypes) + private async Task AddEnumerationOrStructureTypeAsync(INode dataTypeNode, IList serverEnumTypes, IList serverStructTypes, CancellationToken ct = default) { NodeId superType = ExpandedNodeId.ToNodeId(dataTypeNode.NodeId, m_complexTypeResolver.NamespaceUris); while (true) { - superType = m_complexTypeResolver.FindSuperType(superType); + superType = await m_complexTypeResolver.FindSuperTypeAsync(superType, ct).ConfigureAwait(false); if (superType.IsNullNodeId) { throw new ServiceResultException(StatusCodes.BadNodeIdInvalid, $"SuperType for {dataTypeNode.NodeId} not found."); @@ -865,14 +874,15 @@ private Type GetSystemType(ExpandedNodeId nodeId) /// /// Add an enum type defined in a binary schema dictionary. /// - private void AddEnumTypes( + private async Task AddEnumTypesAsync( IComplexTypeBuilder complexTypeBuilder, Dictionary typeDictionary, IList enumList, IList allEnumerationTypes, - IList enumerationTypes) + IList enumerationTypes, + CancellationToken ct = default) { - foreach (var item in enumList) + foreach (Schema.Binary.TypeDescription item in enumList) { Type newType = null; DataTypeNode enumDescription = null; @@ -899,8 +909,8 @@ private void AddEnumTypes( if (newType == null) { // 2. use node cache - var dataTypeNode = m_complexTypeResolver.Find(enumType.NodeId) as DataTypeNode; - newType = AddEnumType(complexTypeBuilder, dataTypeNode); + var dataTypeNode = await m_complexTypeResolver.FindAsync(enumType.NodeId, ct).ConfigureAwait(false) as DataTypeNode; + newType = await AddEnumTypeAsync(complexTypeBuilder, dataTypeNode, ct).ConfigureAwait(false); } } else @@ -933,7 +943,7 @@ private void AddEncodeableType(ExpandedNodeId nodeId, Type type) { return; } - var internalNodeId = NormalizeExpandedNodeId(nodeId); + ExpandedNodeId internalNodeId = NormalizeExpandedNodeId(nodeId); Utils.LogDebug("Adding Type {0} as: {1}", type.FullName, internalNodeId); m_complexTypeResolver.Factory.AddEncodeableType(internalNodeId, type); } @@ -941,9 +951,10 @@ private void AddEncodeableType(ExpandedNodeId nodeId, Type type) /// /// Add an enum type defined in a DataType node. /// - private Type AddEnumType( + private async Task AddEnumTypeAsync( IComplexTypeBuilder complexTypeBuilder, - DataTypeNode enumTypeNode + DataTypeNode enumTypeNode, + CancellationToken ct = default ) { Type newType = null; @@ -956,7 +967,7 @@ DataTypeNode enumTypeNode !(enumTypeNode.DataTypeDefinition?.Body is EnumDefinition enumDefinition)) { // browse for EnumFields or EnumStrings property - object enumTypeArray = m_complexTypeResolver.GetEnumTypeArray(enumTypeNode.NodeId); + object enumTypeArray = await m_complexTypeResolver.GetEnumTypeArrayAsync(enumTypeNode.NodeId, ct).ConfigureAwait(false); if (enumTypeArray is ExtensionObject[] extensionObject) { // 2. use EnumValues @@ -988,18 +999,18 @@ DataTypeNode enumTypeNode /// /// Add structured type to assembly with StructureDefinition. /// - private Type AddStructuredType( + private async Task<(Type structureType, ExpandedNodeIdCollection missingTypes)> AddStructuredTypeAsync( IComplexTypeBuilder complexTypeBuilder, StructureDefinition structureDefinition, QualifiedName typeName, ExpandedNodeId complexTypeId, ExpandedNodeId binaryEncodingId, ExpandedNodeId xmlEncodingId, - out ExpandedNodeIdCollection missingTypes + CancellationToken ct = default ) { // init missing type list - missingTypes = null; + ExpandedNodeIdCollection missingTypes = null; var localDataTypeId = ExpandedNodeId.ToNodeId(complexTypeId, m_complexTypeResolver.NamespaceUris); bool allowSubTypes = IsAllowSubTypes(structureDefinition); @@ -1008,7 +1019,7 @@ out ExpandedNodeIdCollection missingTypes var typeList = new List(); foreach (StructureField field in structureDefinition.Fields) { - Type newType = GetFieldType(field, allowSubTypes); + Type newType = await GetFieldTypeAsync(field, allowSubTypes, ct).ConfigureAwait(false); if (newType == null && !IsRecursiveDataType(localDataTypeId, field.DataType)) { @@ -1029,13 +1040,13 @@ out ExpandedNodeIdCollection missingTypes if (missingTypes != null) { - return null; + return (null, missingTypes); } // Add StructureDefinition to cache m_dataTypeDefinitionCache[localDataTypeId] = structureDefinition; - var fieldBuilder = complexTypeBuilder.AddStructuredType( + IComplexTypeFieldBuilder fieldBuilder = complexTypeBuilder.AddStructuredType( typeName, structureDefinition ); @@ -1043,7 +1054,7 @@ out ExpandedNodeIdCollection missingTypes fieldBuilder.AddTypeIdAttribute(complexTypeId, binaryEncodingId, xmlEncodingId); int order = 1; - var typeListEnumerator = typeList.GetEnumerator(); + List.Enumerator typeListEnumerator = typeList.GetEnumerator(); foreach (StructureField field in structureDefinition.Fields) { typeListEnumerator.MoveNext(); @@ -1051,7 +1062,7 @@ out ExpandedNodeIdCollection missingTypes // check for recursive data type: // field has the same data type as the parent structure var nodeId = ExpandedNodeId.ToNodeId(complexTypeId, m_complexTypeResolver.NamespaceUris); - var isRecursiveDataType = IsRecursiveDataType(nodeId, field.DataType); + bool isRecursiveDataType = IsRecursiveDataType(nodeId, field.DataType); if (isRecursiveDataType) { fieldBuilder.AddField(field, fieldBuilder.GetStructureType(field.ValueRank), order); @@ -1063,7 +1074,7 @@ out ExpandedNodeIdCollection missingTypes order++; } - return fieldBuilder.CreateType(); + return (fieldBuilder.CreateType(), missingTypes); } bool IsAllowSubTypes(StructureDefinition structureDefinition) @@ -1077,9 +1088,9 @@ bool IsAllowSubTypes(StructureDefinition structureDefinition) return false; } - private bool IsAbstractType(NodeId fieldDataType) + private async Task IsAbstractTypeAsync(NodeId fieldDataType, CancellationToken ct = default) { - var dataTypeNode = m_complexTypeResolver.Find(fieldDataType) as DataTypeNode; + var dataTypeNode = await m_complexTypeResolver.FindAsync(fieldDataType, ct).ConfigureAwait(false) as DataTypeNode; return dataTypeNode?.IsAbstract == true; } @@ -1089,7 +1100,7 @@ private bool IsRecursiveDataType(NodeId structureDataType, NodeId fieldDataType) /// /// Determine the type of a field in a StructureField definition. /// - private Type GetFieldType(StructureField field, bool allowSubTypes) + private async Task GetFieldTypeAsync(StructureField field, bool allowSubTypes, CancellationToken ct = default) { if (field.ValueRank != ValueRanks.Scalar && field.ValueRank < ValueRanks.OneDimension) @@ -1103,11 +1114,11 @@ private Type GetFieldType(StructureField field, bool allowSubTypes) if (fieldType == null) { - var superType = GetBuiltInSuperType(field.DataType, allowSubTypes, field.IsOptional); + NodeId superType = await GetBuiltInSuperTypeAsync(field.DataType, allowSubTypes, field.IsOptional, ct).ConfigureAwait(false); if (superType?.IsNullNodeId == false) { field.DataType = superType; - return GetFieldType(field, allowSubTypes); + return await GetFieldTypeAsync(field, allowSubTypes, ct).ConfigureAwait(false); } return null; } @@ -1127,12 +1138,15 @@ private Type GetFieldType(StructureField field, bool allowSubTypes) /// /// Find superType for a datatype. /// - private NodeId GetBuiltInSuperType(NodeId dataType, bool allowSubTypes, bool isOptional) + private async Task GetBuiltInSuperTypeAsync(NodeId dataType, bool allowSubTypes, bool isOptional, CancellationToken ct = default) { + const int MaxSuperTypes = 100; + + int iterations = 0; NodeId superType = dataType; - while (true) + while (iterations++ < MaxSuperTypes) { - superType = m_complexTypeResolver.FindSuperType(superType); + superType = await m_complexTypeResolver.FindSuperTypeAsync(superType, ct).ConfigureAwait(false); if (superType?.IsNullNodeId != false) { return null; @@ -1156,7 +1170,7 @@ private NodeId GetBuiltInSuperType(NodeId dataType, bool allowSubTypes, bool isO // in such case the encoding as ExtensionObject is undetermined and not specified if ((dataType != DataTypeIds.Structure) && ((allowSubTypes && !isOptional) || !allowSubTypes) && - IsAbstractType(dataType)) + await IsAbstractTypeAsync(dataType, ct).ConfigureAwait(false)) { throw new DataTypeNotSupportedException("Invalid definition of a abstract subtype of a structure."); } @@ -1167,10 +1181,21 @@ private NodeId GetBuiltInSuperType(NodeId dataType, bool allowSubTypes, bool isO } return null; } - break; + // end search if a valid BuiltInType is found. Treat type as opaque. + else if (superType.IdType == IdType.Numeric && + (uint)superType.Identifier >= (uint)BuiltInType.Boolean && + (uint)superType.Identifier <= (uint)BuiltInType.DiagnosticInfo) + { + return superType; + } + // no valid supertype found + else if (superType == DataTypeIds.BaseDataType) + { + break; + } } } - return superType; + return null; } /// @@ -1184,11 +1209,11 @@ private void SplitAndSortDictionary( List enumList ) { - foreach (var item in dictionary.TypeDictionary.Items) + foreach (Schema.Binary.TypeDescription item in dictionary.TypeDictionary.Items) { if (item is Schema.Binary.StructuredType structuredObject) { - var dependentFields = structuredObject.Field.Where(f => f.TypeName.Namespace == dictionary.TypeDictionary.TargetNamespace); + IEnumerable dependentFields = structuredObject.Field.Where(f => f.TypeName.Namespace == dictionary.TypeDictionary.TargetNamespace); if (!dependentFields.Any()) { structureList.Insert(0, structuredObject); diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/IComplexTypeResolver.cs b/Libraries/Opc.Ua.Client.ComplexTypes/IComplexTypeResolver.cs index ef31ad695..293b92b55 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/IComplexTypeResolver.cs +++ b/Libraries/Opc.Ua.Client.ComplexTypes/IComplexTypeResolver.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2021 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 @@ -11,7 +11,7 @@ * 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, @@ -30,6 +30,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Opc.Ua.Client.ComplexTypes @@ -53,7 +54,10 @@ public interface IComplexTypeResolver /// Loads all dictionaries of the OPC binary or Xml schema type system. /// /// The type system. Defaults to OPC Binary schema. - Task> LoadDataTypeSystem(NodeId dataTypeSystem = null); + /// + Task> LoadDataTypeSystem( + NodeId dataTypeSystem = null, + CancellationToken ct = default); /// /// Browse for the type and encoding id for a dictionary component. @@ -64,20 +68,16 @@ public interface IComplexTypeResolver /// and data type dictionary. /// To find the typeId and encodingId for a dictionary type definition: /// i) inverse browse the description to get the encodingid - /// ii) from the description inverse browse for encoding - /// to get the subtype typeid - /// iii) load the DataType node + /// ii) from the description inverse browse for encoding + /// to get the subtype typeid + /// iii) load the DataType node /// /// - /// - /// - /// - /// true if successful, false otherwise - bool BrowseTypeIdsForDictionaryComponent( + /// + /// type id, encoding id and data type node if successful, null otherwise + Task<(ExpandedNodeId typeId, ExpandedNodeId encodingId, DataTypeNode dataTypeNode)> BrowseTypeIdsForDictionaryComponentAsync( ExpandedNodeId nodeId, - out ExpandedNodeId typeId, - out ExpandedNodeId encodingId, - out DataTypeNode dataTypeNode); + CancellationToken ct = default); /// /// Browse for the encodings of a datatype list. @@ -85,38 +85,42 @@ bool BrowseTypeIdsForDictionaryComponent( /// /// Is called to allow for caching of encoding information on the client. /// - IList BrowseForEncodings( + Task> BrowseForEncodingsAsync( IList nodeIds, - string[] supportedEncodings); - + string[] supportedEncodings, + CancellationToken ct = default); + /// /// Browse for the encodings of a type. /// /// /// Browse for binary encoding of a structure datatype. /// - IList BrowseForEncodings( + Task<(IList encodings, ExpandedNodeId binaryEncodingId, ExpandedNodeId xmlEncodingId)> BrowseForEncodingsAsync( ExpandedNodeId nodeId, string[] supportedEncodings, - out ExpandedNodeId binaryEncodingId, - out ExpandedNodeId xmlEncodingId); + CancellationToken ct = default); /// /// Load all subTypes and optionally nested subtypes of a type definition. /// Filter for all subtypes or only subtypes outside the default namespace. /// - IList LoadDataTypes( + Task> LoadDataTypesAsync( ExpandedNodeId dataType, bool nestedSubTypes = false, bool addRootNode = false, - bool filterUATypes = true); + bool filterUATypes = true, + CancellationToken ct = default); /// /// Finds a node in the node set. /// /// The node identifier. + /// /// Returns null if the node does not exist. - INode Find(ExpandedNodeId nodeId); + Task FindAsync( + ExpandedNodeId nodeId, + CancellationToken ct = default); /// /// Reads the enum type array of a enum type definition node. @@ -126,19 +130,21 @@ IList LoadDataTypes( /// reference of the enum type NodeId /// /// The enum type nodeId which has an enum array in the property. + /// /// /// The value of the nodeId, which can be an array of /// or of . - /// null if the enum type array does not exist. + /// null if the enum type array does not exist. /// - object GetEnumTypeArray(ExpandedNodeId nodeId); + Task GetEnumTypeArrayAsync(ExpandedNodeId nodeId, CancellationToken ct = default); /// /// Returns the immediate supertype for the type. /// /// The type identifier. + /// /// The immediate supertype identifier for - NodeId FindSuperType(NodeId typeId); + Task FindSuperTypeAsync(NodeId typeId, CancellationToken ct = default); } }//namespace diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/AttributeExtensions.cs b/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/AttributeExtensions.cs index 53ebf6d7b..c7f1bd493 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/AttributeExtensions.cs +++ b/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/AttributeExtensions.cs @@ -282,7 +282,8 @@ private static BuiltInType GetBuiltInType(NodeId datatypeId) // supertypes of numbers case DataTypes.Integer: case DataTypes.UInteger: - case DataTypes.Number: return BuiltInType.Variant; + case DataTypes.Number: + case DataTypes.Decimal: return BuiltInType.Variant; } return TypeInfo.GetBuiltInType(datatypeId); diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeBuilder.cs b/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeBuilder.cs index 472b2e55e..455132113 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeBuilder.cs +++ b/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeBuilder.cs @@ -117,7 +117,7 @@ public IComplexTypeFieldBuilder AddStructuredType( } var structureBuilder = m_moduleBuilder.DefineType( GetFullQualifiedTypeName(name), - TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable, + TypeAttributes.Public | TypeAttributes.Class, baseType); structureBuilder.DataContractAttribute(m_targetNamespace); structureBuilder.StructureDefinitionAttribute(structureDefinition); diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/TypeResolver/NodeCacheResolver.cs b/Libraries/Opc.Ua.Client.ComplexTypes/TypeResolver/NodeCacheResolver.cs index 05472f5ec..72750c96f 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/TypeResolver/NodeCacheResolver.cs +++ b/Libraries/Opc.Ua.Client.ComplexTypes/TypeResolver/NodeCacheResolver.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2021 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 @@ -11,7 +11,7 @@ * 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, @@ -32,6 +32,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace Opc.Ua.Client.ComplexTypes @@ -65,106 +66,107 @@ private void Initialize(ISession session) public IEncodeableFactory Factory => m_session.Factory; /// - public Task> LoadDataTypeSystem(NodeId dataTypeSystem = null) + public Task> LoadDataTypeSystem( + NodeId dataTypeSystem = null, + CancellationToken ct = default) { - return m_session.LoadDataTypeSystem(dataTypeSystem); + return m_session.LoadDataTypeSystem(dataTypeSystem, ct); } /// - public IList BrowseForEncodings( + public async Task> BrowseForEncodingsAsync( IList nodeIds, - string[] supportedEncodings - ) + string[] supportedEncodings, + CancellationToken ct = default) { // cache type encodings - var encodings = m_session.NodeCache.FindReferences( + IList encodings = await m_session.NodeCache.FindReferencesAsync( nodeIds, new NodeIdCollection { ReferenceTypeIds.HasEncoding }, false, - false - ); + false, + ct).ConfigureAwait(false); // cache dictionary descriptions nodeIds = encodings.Select(r => r.NodeId).ToList(); - var descriptions = m_session.NodeCache.FindReferences( + IList descriptions = await m_session.NodeCache.FindReferencesAsync( nodeIds, new NodeIdCollection { ReferenceTypeIds.HasDescription }, false, - false - ); + false, + ct).ConfigureAwait(false); return encodings.Where(r => supportedEncodings.Contains(r.BrowseName.Name)) .Select(r => ExpandedNodeId.ToNodeId(r.NodeId, m_session.NamespaceUris)).ToList(); } /// - public IList BrowseForEncodings( + public async Task<(IList encodings, ExpandedNodeId binaryEncodingId, ExpandedNodeId xmlEncodingId)> BrowseForEncodingsAsync( ExpandedNodeId nodeId, string[] supportedEncodings, - out ExpandedNodeId binaryEncodingId, - out ExpandedNodeId xmlEncodingId) + CancellationToken ct = default) { - var references = m_session.NodeCache.FindReferences( - nodeId, - ReferenceTypeIds.HasEncoding, - false, - false - ); + IList references = await m_session.NodeCache.FindReferencesAsync( + nodeId, + ReferenceTypeIds.HasEncoding, + false, + false, + ct).ConfigureAwait(false); - binaryEncodingId = references.FirstOrDefault(r => r.BrowseName.Name == BrowseNames.DefaultBinary)?.NodeId; + ExpandedNodeId binaryEncodingId = references.FirstOrDefault(r => r.BrowseName.Name == BrowseNames.DefaultBinary)?.NodeId; binaryEncodingId = NormalizeExpandedNodeId(binaryEncodingId); - xmlEncodingId = references.FirstOrDefault(r => r.BrowseName.Name == BrowseNames.DefaultXml)?.NodeId; + ExpandedNodeId xmlEncodingId = references.FirstOrDefault(r => r.BrowseName.Name == BrowseNames.DefaultXml)?.NodeId; xmlEncodingId = NormalizeExpandedNodeId(xmlEncodingId); - return references.Where(r => supportedEncodings.Contains(r.BrowseName.Name)) - .Select(r => ExpandedNodeId.ToNodeId(r.NodeId, m_session.NamespaceUris)).ToList(); + return (references + .Where(r => supportedEncodings.Contains(r.BrowseName.Name)) + .Select(r => ExpandedNodeId.ToNodeId(r.NodeId, m_session.NamespaceUris)) + .ToList(), binaryEncodingId, xmlEncodingId); } /// - public bool BrowseTypeIdsForDictionaryComponent( + public async Task<(ExpandedNodeId typeId, ExpandedNodeId encodingId, DataTypeNode dataTypeNode)> BrowseTypeIdsForDictionaryComponentAsync( ExpandedNodeId nodeId, - out ExpandedNodeId typeId, - out ExpandedNodeId encodingId, - out DataTypeNode dataTypeNode) + CancellationToken ct = default) { - typeId = ExpandedNodeId.Null; - encodingId = ExpandedNodeId.Null; - dataTypeNode = null; + ExpandedNodeId encodingId; + DataTypeNode dataTypeNode; - var references = m_session.NodeCache.FindReferences( + IList references = await m_session.NodeCache.FindReferencesAsync( nodeId, ReferenceTypeIds.HasDescription, true, - false - ); + false, + ct).ConfigureAwait(false); if (references.Count == 1) { encodingId = references[0].NodeId; - references = m_session.NodeCache.FindReferences( + references = await m_session.NodeCache.FindReferencesAsync( encodingId, ReferenceTypeIds.HasEncoding, true, - false - ); + false, + ct).ConfigureAwait(false); encodingId = NormalizeExpandedNodeId(encodingId); if (references.Count == 1) { - typeId = references[0].NodeId; + ExpandedNodeId typeId = references[0].NodeId; dataTypeNode = m_session.NodeCache.Find(typeId) as DataTypeNode; typeId = NormalizeExpandedNodeId(typeId); - return true; + return (typeId, encodingId, dataTypeNode); } } - return false; + return (null, null, null); } /// - public IList LoadDataTypes( + public async Task> LoadDataTypesAsync( ExpandedNodeId dataType, bool nestedSubTypes = false, bool addRootNode = false, - bool filterUATypes = true) + bool filterUATypes = true, + CancellationToken ct = default) { var result = new List(); var nodesToBrowse = new ExpandedNodeIdCollection { @@ -178,7 +180,7 @@ public IList LoadDataTypes( if (addRootNode) { - var rootNode = m_session.NodeCache.Find(dataType); + INode rootNode = await m_session.NodeCache.FindAsync(dataType, ct).ConfigureAwait(false); if (!(rootNode is DataTypeNode)) { throw new ServiceResultException("Root Node is not a DataType node."); @@ -188,11 +190,12 @@ public IList LoadDataTypes( while (nodesToBrowse.Count > 0) { - var response = m_session.NodeCache.FindReferences( + IList response = await m_session.NodeCache.FindReferencesAsync( nodesToBrowse, new NodeIdCollection { ReferenceTypeIds.HasSubtype }, false, - false); + false, + ct).ConfigureAwait(false); var nextNodesToBrowse = new ExpandedNodeIdCollection(); if (nestedSubTypes) @@ -221,35 +224,35 @@ public IList LoadDataTypes( } /// - public INode Find(ExpandedNodeId nodeId) + public Task FindAsync(ExpandedNodeId nodeId, CancellationToken ct) { - return m_session.NodeCache.Find(nodeId); + return m_session.NodeCache.FindAsync(nodeId, ct); } /// - public object GetEnumTypeArray(ExpandedNodeId nodeId) + public async Task GetEnumTypeArrayAsync(ExpandedNodeId nodeId, CancellationToken ct = default) { // find the property reference for the enum type - var references = m_session.NodeCache.FindReferences( + IList references = await m_session.NodeCache.FindReferencesAsync( nodeId, ReferenceTypeIds.HasProperty, false, - false - ); - var property = references.FirstOrDefault(); + false, + ct).ConfigureAwait(false); + INode property = references.FirstOrDefault(); if (property != null) { // read the enum type array - DataValue value = m_session.ReadValue(ExpandedNodeId.ToNodeId(property.NodeId, NamespaceUris)); + DataValue value = await m_session.ReadValueAsync(ExpandedNodeId.ToNodeId(property.NodeId, NamespaceUris), ct).ConfigureAwait(false); return value?.Value; } return null; } /// - public NodeId FindSuperType(NodeId typeId) + public Task FindSuperTypeAsync(NodeId typeId, CancellationToken ct = default) { - return m_session.NodeCache.FindSuperType(typeId); + return m_session.NodeCache.FindSuperTypeAsync(typeId, ct); } #endregion IComplexTypeResolver diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Types/UnionComplexType.cs b/Libraries/Opc.Ua.Client.ComplexTypes/Types/UnionComplexType.cs index c7f78e2cb..9b3da52ef 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/UnionComplexType.cs +++ b/Libraries/Opc.Ua.Client.ComplexTypes/Types/UnionComplexType.cs @@ -147,14 +147,14 @@ public override void Decode(IDecoder decoder) } /// - public override bool IsEqual(IEncodeable equalValue) + public override bool IsEqual(IEncodeable encodeable) { - if (Object.ReferenceEquals(this, equalValue)) + if (Object.ReferenceEquals(this, encodeable)) { return true; } - if (!(equalValue is UnionComplexType valueBaseType)) + if (!(encodeable is UnionComplexType valueBaseType)) { return false; } diff --git a/Libraries/Opc.Ua.Client/DataDictionary.cs b/Libraries/Opc.Ua.Client/DataDictionary.cs index e6e024e2e..e1d407879 100644 --- a/Libraries/Opc.Ua.Client/DataDictionary.cs +++ b/Libraries/Opc.Ua.Client/DataDictionary.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -32,6 +32,7 @@ using System.IO; using System.Linq; using System.Runtime.Serialization; +using System.Threading; using System.Threading.Tasks; using Opc.Ua.Schema; @@ -102,20 +103,20 @@ private void Initialize() /// /// Loads the dictionary identified by the node id. /// - public Task Load(INode dictionary) + public void Load(INode dictionary) { if (dictionary == null) { throw new ArgumentNullException(nameof(dictionary)); } NodeId dictionaryId = ExpandedNodeId.ToNodeId(dictionary.NodeId, m_session.NamespaceUris); - return Load(dictionaryId, dictionary.ToString()); + Load(dictionaryId, dictionary.ToString()); } /// /// Loads the dictionary identified by the node id. /// - public async Task Load(NodeId dictionaryId, string name, byte[] schema = null, IDictionary imports = null) + public void Load(NodeId dictionaryId, string name, byte[] schema = null, IDictionary imports = null) { if (dictionaryId == null) { @@ -141,7 +142,7 @@ public async Task Load(NodeId dictionaryId, string name, byte[] schema = null, I Array.Resize(ref schema, zeroTerminator); } - await Validate(schema, imports).ConfigureAwait(false); + Validate(schema, imports); ReadDataTypes(dictionaryId); @@ -225,7 +226,10 @@ private void ReadDataTypes(NodeId dictionaryId) /// /// Reads the contents of multiple data dictionaries. /// - public static async Task> ReadDictionaries(ISessionClientMethods session, IList dictionaryIds) + public static async Task> ReadDictionaries( + ISessionClientMethods session, + IList dictionaryIds, + CancellationToken ct = default) { ReadValueIdCollection itemsToRead = new ReadValueIdCollection(); foreach (var nodeId in dictionaryIds) @@ -240,17 +244,28 @@ public static async Task> ReadDictionaries(ISessionC itemsToRead.Add(itemToRead); } +#if CLIENT_ASYNC // read values. ReadResponse readResponse = await session.ReadAsync( null, 0, TimestampsToReturn.Neither, itemsToRead, - System.Threading.CancellationToken.None).ConfigureAwait(false); + ct).ConfigureAwait(false); DataValueCollection values = readResponse.Results; DiagnosticInfoCollection diagnosticInfos = readResponse.DiagnosticInfos; - + ResponseHeader response = readResponse.ResponseHeader; +#else + // read values. + ResponseHeader response = session.Read( + null, + 0, + TimestampsToReturn.Neither, + itemsToRead, + out DataValueCollection values, + out DiagnosticInfoCollection diagnosticInfos); +#endif ClientBase.ValidateResponse(values, itemsToRead); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, itemsToRead); @@ -262,7 +277,7 @@ public static async Task> ReadDictionaries(ISessionC // check for error. if (StatusCode.IsBad(values[ii].StatusCode)) { - ServiceResult sr = ClientBase.GetResult(values[ii].StatusCode, 0, diagnosticInfos, readResponse.ResponseHeader); + ServiceResult sr = ClientBase.GetResult(values[ii].StatusCode, 0, diagnosticInfos, response); throw new ServiceResultException(sr); } @@ -322,9 +337,9 @@ public byte[] ReadDictionary(NodeId dictionaryId) /// /// The encoded dictionary to validate. /// Throw if an error occurred. - internal Task Validate(byte[] dictionary, bool throwOnError) + internal void Validate(byte[] dictionary, bool throwOnError) { - return Validate(dictionary, null, throwOnError); + Validate(dictionary, null, throwOnError); } /// @@ -333,7 +348,7 @@ internal Task Validate(byte[] dictionary, bool throwOnError) /// The encoded dictionary to validate. /// A table of imported namespace schemas. /// Throw if an error occurred. - internal async Task Validate(byte[] dictionary, IDictionary imports = null, bool throwOnError = false) + internal void Validate(byte[] dictionary, IDictionary imports = null, bool throwOnError = false) { MemoryStream istrm = new MemoryStream(dictionary); @@ -362,7 +377,7 @@ internal async Task Validate(byte[] dictionary, IDictionary impo var validator = new Schema.Binary.BinarySchemaValidator(imports); try { - await validator.Validate(istrm).ConfigureAwait(false); + validator.Validate(istrm); } catch (Exception e) { @@ -377,7 +392,7 @@ internal async Task Validate(byte[] dictionary, IDictionary impo TypeDictionary = validator.Dictionary; } } - #endregion +#endregion #region Private Members private ISession m_session; diff --git a/Libraries/Opc.Ua.Client/DefaultSessionFactory.cs b/Libraries/Opc.Ua.Client/DefaultSessionFactory.cs index 5f2788717..a498a8484 100644 --- a/Libraries/Opc.Ua.Client/DefaultSessionFactory.cs +++ b/Libraries/Opc.Ua.Client/DefaultSessionFactory.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2022 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 @@ -11,7 +11,7 @@ * 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, @@ -38,7 +38,7 @@ namespace Opc.Ua.Client /// /// Object that creates instances of an Opc.Ua.Client.Session object. /// - public class DefaultSessionFactory : ISessionFactory + public class DefaultSessionFactory : ISessionFactory, ISessionInstantiator { /// /// The default instance of the factory. @@ -54,7 +54,7 @@ protected DefaultSessionFactory() #region ISessionFactory Members /// - public async virtual Task CreateAsync( + public virtual Task CreateAsync( ApplicationConfiguration configuration, ConfiguredEndpoint endpoint, bool updateBeforeConnect, @@ -64,8 +64,7 @@ public async virtual Task CreateAsync( IList preferredLocales, CancellationToken ct = default) { - return await Session.Create(configuration, endpoint, updateBeforeConnect, false, - sessionName, sessionTimeout, identity, preferredLocales).ConfigureAwait(false); + return CreateAsync(configuration, endpoint, updateBeforeConnect, false, sessionName, sessionTimeout, identity, preferredLocales, ct); } /// @@ -80,7 +79,7 @@ public async virtual Task CreateAsync( IList preferredLocales, CancellationToken ct = default) { - return await Session.Create(configuration, (ITransportWaitingConnection)null, endpoint, + return await Session.Create(this, configuration, (ITransportWaitingConnection)null, endpoint, updateBeforeConnect, checkDomain, sessionName, sessionTimeout, identity, preferredLocales, ct).ConfigureAwait(false); } @@ -98,7 +97,7 @@ public async virtual Task CreateAsync( IList preferredLocales, CancellationToken ct = default) { - return await Session.Create(configuration, connection, endpoint, + return await Session.Create(this, configuration, connection, endpoint, updateBeforeConnect, checkDomain, sessionName, sessionTimeout, identity, preferredLocales, ct ).ConfigureAwait(false); @@ -166,7 +165,7 @@ public virtual ISession Create( EndpointDescriptionCollection availableEndpoints = null, StringCollection discoveryProfileUris = null) { - return Session.Create(configuration, channel, endpoint, clientCertificate, availableEndpoints, discoveryProfileUris); + return Session.Create(this, configuration, channel, endpoint, clientCertificate, availableEndpoints, discoveryProfileUris); } /// @@ -182,35 +181,58 @@ public virtual Task CreateChannelAsync( } /// - public virtual Task RecreateAsync(ISession sessionTemplate, CancellationToken ct = default) + public virtual async Task RecreateAsync(ISession sessionTemplate, CancellationToken ct = default) { if (!(sessionTemplate is Session template)) { throw new ArgumentOutOfRangeException(nameof(sessionTemplate), "The ISession provided is not of a supported type."); } - return Task.FromResult((ISession)Session.Recreate(template)); + return await Session.RecreateAsync(template, ct).ConfigureAwait(false); } /// - public virtual Task RecreateAsync(ISession sessionTemplate, ITransportWaitingConnection connection, CancellationToken ct = default) + public virtual async Task RecreateAsync(ISession sessionTemplate, ITransportWaitingConnection connection, CancellationToken ct = default) { if (!(sessionTemplate is Session template)) { throw new ArgumentOutOfRangeException(nameof(sessionTemplate), "The ISession provided is not of a supported type"); } - return Task.FromResult((ISession)Session.Recreate(template, connection)); + return await Session.RecreateAsync(template, connection, ct).ConfigureAwait(false); } /// - public virtual Task RecreateAsync(ISession sessionTemplate, ITransportChannel transportChannel, CancellationToken ct = default) + public virtual async Task RecreateAsync(ISession sessionTemplate, ITransportChannel transportChannel, CancellationToken ct = default) { if (!(sessionTemplate is Session template)) { throw new ArgumentOutOfRangeException(nameof(sessionTemplate), "The ISession provided is not of a supported type"); } - return Task.FromResult((ISession)Session.Recreate(template, transportChannel)); + return await Session.RecreateAsync(template, transportChannel, ct).ConfigureAwait(false); + } + #endregion + + #region ISessionInstantiator Members + /// + public virtual Session Create( + ISessionChannel channel, + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint) + { + return new Session(channel, configuration, endpoint); + } + + /// + public virtual Session Create( + ITransportChannel channel, + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint, + X509Certificate2 clientCertificate, + EndpointDescriptionCollection availableEndpoints = null, + StringCollection discoveryProfileUris = null) + { + return new Session(channel, configuration, endpoint, clientCertificate, availableEndpoints, discoveryProfileUris); } #endregion } diff --git a/Libraries/Opc.Ua.Client/INodeCache.cs b/Libraries/Opc.Ua.Client/INodeCache.cs index b3c6e7301..d507a3607 100644 --- a/Libraries/Opc.Ua.Client/INodeCache.cs +++ b/Libraries/Opc.Ua.Client/INodeCache.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -28,6 +28,8 @@ * ======================================================================*/ using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace Opc.Ua.Client { @@ -64,6 +66,44 @@ public interface INodeCache : INodeTable, ITypeTable /// IList FetchNodes(IList nodeIds); +#if (CLIENT_ASYNC) + /// + /// Finds a set of nodes in the nodeset, + /// fetches missing nodes from server. + /// + /// The node identifier. + /// + Task FindAsync(ExpandedNodeId nodeId, CancellationToken ct = default); + + /// + /// Finds a set of nodes in the nodeset, + /// fetches missing nodes from server. + /// + /// The node identifier collection. + /// + Task> FindAsync(IList nodeIds, CancellationToken ct = default); + + /// + /// Fetches a node from the server and updates the cache. + /// + /// Node id to fetch. + /// + Task FetchNodeAsync(ExpandedNodeId nodeId, CancellationToken ct = default); + + /// + /// Fetches a node collection from the server and updates the cache. + /// + /// The node identifier collection. + /// + Task> FetchNodesAsync(IList nodeIds, CancellationToken ct = default); + + /// + /// Adds the supertypes of the node to the cache. + /// + /// Node id to fetch. + /// + Task FetchSuperTypesAsync(ExpandedNodeId nodeId, CancellationToken ct = default); +#endif /// /// Adds the supertypes of the node to the cache. /// @@ -79,6 +119,18 @@ public interface INodeCache : INodeTable, ITypeTable /// IList FindReferences(IList nodeIds, IList referenceTypeIds, bool isInverse, bool includeSubtypes); +#if (CLIENT_ASYNC) + /// + /// Returns the references of the specified node that meet the criteria specified. + /// + Task> FindReferencesAsync(ExpandedNodeId nodeId, NodeId referenceTypeId, bool isInverse, bool includeSubtypes, CancellationToken ct = default); + + /// + /// Returns the references of the specified nodes that meet the criteria specified. + /// + Task> FindReferencesAsync(IList nodeIds, IList referenceTypeIds, bool isInverse, bool includeSubtypes, CancellationToken ct = default); +#endif + /// /// Returns a display name for a node. /// diff --git a/Libraries/Opc.Ua.Client/ISession.cs b/Libraries/Opc.Ua.Client/ISession.cs index ff37758db..cd6c4cbff 100644 --- a/Libraries/Opc.Ua.Client/ISession.cs +++ b/Libraries/Opc.Ua.Client/ISession.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2022 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 @@ -11,7 +11,7 @@ * 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, @@ -23,7 +23,7 @@ * 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: + * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ @@ -63,7 +63,7 @@ namespace Opc.Ua.Client /// /// Manages a session with a server. /// - public interface ISession : ISessionClient, IDisposable + public interface ISession : ISessionClient { #region Events /// @@ -99,7 +99,7 @@ public interface ISession : ISessionClient, IDisposable event PublishErrorEventHandler PublishError; /// - /// Raised when a publish request is about to acknolegde sequence numbers. + /// Raised when a publish request is about to acknowledge sequence numbers. /// /// /// If the client chose to defer acknowledge of sequenece numbers, it is responsible @@ -219,7 +219,7 @@ public interface ISession : ISessionClient, IDisposable int SubscriptionCount { get; } /// - /// If the subscriptions are deleted when a session is closed. + /// If the subscriptions are deleted when a session is closed. /// bool DeleteSubscriptionsOnClose { get; set; } @@ -277,12 +277,12 @@ public interface ISession : ISessionClient, IDisposable OperationLimits OperationLimits { get; } /// - /// If the subscriptions are transferred when a session is reconnected. + /// If the subscriptions are transferred when a session is reconnected. /// /// /// Default false, set to true if subscriptions should /// be transferred after reconnect. Service must be supported by server. - /// + /// bool TransferSubscriptionsOnReconnect { get; set; } /// @@ -314,37 +314,57 @@ public interface ISession : ISessionClient, IDisposable /// void Reconnect(ITransportChannel channel); +#if (CLIENT_ASYNC) + /// + /// Reconnects to the server after a network failure. + /// + Task ReconnectAsync(CancellationToken ct = default); + + /// + /// Reconnects to the server after a network failure using a waiting connection. + /// + Task ReconnectAsync(ITransportWaitingConnection connection, CancellationToken ct = default); + + /// + /// Reconnects to the server using a new channel. + /// + Task ReconnectAsync(ITransportChannel channel, CancellationToken ct = default); +#endif + /// /// Saves all the subscriptions of the session. /// /// The file path. - void Save(string filePath); + /// + void Save(string filePath, IEnumerable knownTypes = null); /// /// Saves a set of subscriptions to a stream. /// - void Save(Stream stream, IEnumerable subscriptions); + void Save(Stream stream, IEnumerable subscriptions, IEnumerable knownTypes = null); /// /// Saves a set of subscriptions to a file. /// - void Save(string filePath, IEnumerable subscriptions); + void Save(string filePath, IEnumerable subscriptions, IEnumerable knownTypes = null); /// /// Load the list of subscriptions saved in a stream. /// /// The stream. /// Load the subscriptions for transfer after load. + /// Additional known types that may be needed to read the saved subscriptions. /// The list of loaded subscriptions - IEnumerable Load(Stream stream, bool transferSubscriptions = false); + IEnumerable Load(Stream stream, bool transferSubscriptions = false, IEnumerable knownTypes = null); /// /// Load the list of subscriptions saved in a file. /// /// The file path. /// Load the subscriptions for transfer after load. + /// Additional known types that may be needed to read the saved subscriptions. /// The list of loaded subscriptions - IEnumerable Load(string filePath, bool transferSubscriptions = false); + IEnumerable Load(string filePath, bool transferSubscriptions = false, IEnumerable knownTypes = null); /// /// Returns the active session configuration and writes it to a stream. @@ -378,6 +398,30 @@ public interface ISession : ISessionClient, IDisposable /// void FetchTypeTree(ExpandedNodeIdCollection typeIds); +#if (CLIENT_ASYNC) + /// + /// Updates the local copy of the server's namespace uri and server uri tables. + /// + /// The cancellation token. + Task FetchNamespaceTablesAsync(CancellationToken ct = default); + + /// + /// Updates the cache with the type and its subtypes. + /// + /// + /// This method can be used to ensure the TypeTree is populated. + /// + Task FetchTypeTreeAsync(ExpandedNodeId typeId, CancellationToken ct = default); + + /// + /// Updates the cache with the types and its subtypes. + /// + /// + /// This method can be used to ensure the TypeTree is populated. + /// + Task FetchTypeTreeAsync(ExpandedNodeIdCollection typeIds, CancellationToken ct = default); +#endif + /// /// Returns the available encodings for a node /// @@ -390,11 +434,13 @@ public interface ISession : ISessionClient, IDisposable /// The encoding Id. ReferenceDescription FindDataDescription(NodeId encodingId); +#if (CLIENT_ASYNC) /// /// Returns the data dictionary that contains the description. /// /// The description id. - Task FindDataDictionary(NodeId descriptionId); + /// + Task FindDataDictionary(NodeId descriptionId, CancellationToken ct = default); /// /// Returns the data dictionary that contains the description. @@ -402,13 +448,19 @@ public interface ISession : ISessionClient, IDisposable /// The dictionary id. /// /// The dictionary. - Task LoadDataDictionary(ReferenceDescription dictionaryNode, bool forceReload = false); + DataDictionary LoadDataDictionary( + ReferenceDescription dictionaryNode, + bool forceReload = false); /// /// Loads all dictionaries of the OPC binary or Xml schema type system. /// /// The type system. - Task> LoadDataTypeSystem(NodeId dataTypeSystem = null); + /// + Task> LoadDataTypeSystem( + NodeId dataTypeSystem = null, + CancellationToken ct = default); +#endif /// /// Reads the values for the node attributes and returns a node object. @@ -488,6 +540,23 @@ public interface ISession : ISessionClient, IDisposable /// The errors reported by the server. void FetchReferences(IList nodeIds, out IList referenceDescriptions, out IList errors); +#if (CLIENT_ASYNC) + /// + /// Fetches all references for the specified node. + /// + /// The node id. + /// + Task FetchReferencesAsync(NodeId nodeId, CancellationToken ct); + + /// + /// Fetches all references for the specified nodes. + /// + /// The node id collection. + /// + /// A list of reference collections and the errors reported by the server. + Task<(IList, IList)> FetchReferencesAsync(IList nodeIds, CancellationToken ct); +#endif + /// /// Establishes a session with the server. /// @@ -547,6 +616,35 @@ public interface ISession : ISessionClient, IDisposable void ReadDisplayName(IList nodeIds, out IList displayNames, out IList errors); #if (CLIENT_ASYNC) + /// + /// Establishes a session with the server. + /// + /// The name to assign to the session. + /// The user identity. + /// The cancellation token. + Task OpenAsync(string sessionName, IUserIdentity identity, CancellationToken ct); + + /// + /// Establishes a session with the server. + /// + /// The name to assign to the session. + /// The session timeout. + /// The user identity. + /// The list of preferred locales. + /// The cancellation token. + Task OpenAsync(string sessionName, uint sessionTimeout, IUserIdentity identity, IList preferredLocales, CancellationToken ct); + + /// + /// Establishes a session with the server. + /// + /// The name to assign to the session. + /// The session timeout. + /// The user identity. + /// The list of preferred locales. + /// If set to true then the domain in the certificate must match the endpoint used. + /// The cancellation token. + Task OpenAsync(string sessionName, uint sessionTimeout, IUserIdentity identity, IList preferredLocales, bool checkDomain, CancellationToken ct); + /// /// Reads the values for the node attributes and returns a node object collection. /// @@ -853,6 +951,11 @@ ResponseHeader EndBrowseNext( bool ResendData(IEnumerable subscriptions, out IList errors); #if CLIENT_ASYNC + /// + /// Sends a republish request. + /// + Task RepublishAsync(uint subscriptionId, uint sequenceNumber, CancellationToken ct = default); + /// /// Call the ResendData method on the server for all subscriptions. /// diff --git a/Libraries/Opc.Ua.Client/ISessionFactory.cs b/Libraries/Opc.Ua.Client/ISessionFactory.cs index 93d1ede1d..ed7f917bd 100644 --- a/Libraries/Opc.Ua.Client/ISessionFactory.cs +++ b/Libraries/Opc.Ua.Client/ISessionFactory.cs @@ -175,27 +175,27 @@ Task CreateAsync( /// /// Recreates a session based on a specified template. /// - /// The ISession object to use as template + /// The ISession object to use as template /// The cancellation token. /// The new session object. - Task RecreateAsync(ISession template, CancellationToken ct = default); + Task RecreateAsync(ISession sessionTemplate, CancellationToken ct = default); /// /// Recreates a session based on a specified template. /// - /// The ISession object to use as template + /// The ISession object to use as template /// The waiting reverse connection. /// The cancellation token. /// The new session object. - Task RecreateAsync(ISession template, ITransportWaitingConnection connection, CancellationToken ct = default); + Task RecreateAsync(ISession sessionTemplate, ITransportWaitingConnection connection, CancellationToken ct = default); /// /// Recreates a session based on a specified template using the provided channel. /// - /// The Session object to use as template + /// The Session object to use as template /// The channel to use to recreate the session. /// The cancellation token. /// The new session object. - Task RecreateAsync(ISession template, ITransportChannel transportChannel, CancellationToken ct = default); + Task RecreateAsync(ISession sessionTemplate, ITransportChannel transportChannel, CancellationToken ct = default); } } diff --git a/Libraries/Opc.Ua.Client/ISessionInstantiator.cs b/Libraries/Opc.Ua.Client/ISessionInstantiator.cs new file mode 100644 index 000000000..d9ab2af0a --- /dev/null +++ b/Libraries/Opc.Ua.Client/ISessionInstantiator.cs @@ -0,0 +1,62 @@ +/* ======================================================================== + * Copyright (c) 2005-2023 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.Security.Cryptography.X509Certificates; + +namespace Opc.Ua.Client +{ + /// + /// Object that creates an instance of a Session object. + /// It can be used to subclass enhanced Session + /// classes which survive reconnect handling etc. + /// + public interface ISessionInstantiator + { + #region Constructors + /// + /// Constructs a new instance of the class. + /// + Session Create( + ISessionChannel channel, + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint); + + /// + /// Constructs a new instance of the class. + /// + Session Create( + ITransportChannel channel, + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint, + X509Certificate2 clientCertificate, + EndpointDescriptionCollection availableEndpoints = null, + StringCollection discoveryProfileUris = null); + #endregion + } +} diff --git a/Libraries/Opc.Ua.Client/MonitoredItem.cs b/Libraries/Opc.Ua.Client/MonitoredItem.cs index 0bc056ffd..bd4f8face 100644 --- a/Libraries/Opc.Ua.Client/MonitoredItem.cs +++ b/Libraries/Opc.Ua.Client/MonitoredItem.cs @@ -143,7 +143,7 @@ public MonitoredItem(MonitoredItem template, bool copyEventHandlers, bool copyCl /// Called by the .NET framework during deserialization. /// [OnDeserializing] - private void Initialize(StreamingContext context) + protected void Initialize(StreamingContext context) { // object initializers are not called during deserialization. m_cache = new object(); @@ -686,6 +686,15 @@ public virtual object Clone() { return new MonitoredItem(this); } + + /// + /// Clones a monitored item or the subclass with an option to copy event handlers. + /// + /// A cloned instance of the monitored item or a subclass. + public virtual MonitoredItem CloneMonitoredItem(bool copyEventHandlers, bool copyClientHandle) + { + return new MonitoredItem(this, copyEventHandlers, copyClientHandle); + } #endregion #region Public Methods diff --git a/Libraries/Opc.Ua.Client/NodeCache.cs b/Libraries/Opc.Ua.Client/NodeCache.cs index 2f57f4491..f8724386a 100644 --- a/Libraries/Opc.Ua.Client/NodeCache.cs +++ b/Libraries/Opc.Ua.Client/NodeCache.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -38,7 +38,7 @@ namespace Opc.Ua.Client /// /// An implementation of a client side nodecache. /// - public class NodeCache : INodeCache + public partial class NodeCache : INodeCache, IDisposable { #region Constructors /// @@ -54,17 +54,27 @@ public NodeCache(ISession session) m_uaTypesLoaded = false; m_cacheLock = new ReaderWriterLockSlim(); } + #endregion + #region IDisposable /// - /// Destructor to clean up. + /// An overrideable version of the Dispose. /// - ~NodeCache() + protected virtual void Dispose(bool disposing) { - if (m_cacheLock != null) + if (disposing) { - m_cacheLock.Dispose(); + m_session = null; + m_cacheLock?.Dispose(); } } + + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } #endregion #region INodeTable Members @@ -870,7 +880,6 @@ public IList FetchNodes(IList nodeIds) m_session.ReadNodes(localIds, out IList sourceNodes, out IList readErrors); m_session.FetchReferences(localIds, out IList referenceCollectionList, out IList fetchErrors); - int ii = 0; for (ii = 0; ii < count; ii++) { diff --git a/Libraries/Opc.Ua.Client/NodeCacheAsync.cs b/Libraries/Opc.Ua.Client/NodeCacheAsync.cs new file mode 100644 index 000000000..95cd3f19b --- /dev/null +++ b/Libraries/Opc.Ua.Client/NodeCacheAsync.cs @@ -0,0 +1,468 @@ +/* ======================================================================== + * Copyright (c) 2005-2023 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/ + * ======================================================================*/ + +#if CLIENT_ASYNC + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Opc.Ua.Client +{ + /// + /// An implementation of a client side nodecache. + /// + public partial class NodeCache : INodeCache + { + /// + public async Task FindAsync(ExpandedNodeId nodeId, CancellationToken ct = default) + { + // check for null. + if (NodeId.IsNull(nodeId)) + { + return null; + } + + INode node; + try + { + m_cacheLock.EnterReadLock(); + + // check if node alredy exists. + node = m_nodes.Find(nodeId); + } + finally + { + m_cacheLock.ExitReadLock(); + } + + if (node != null) + { + // do not return temporary nodes created after a Browse(). + if (node.GetType() != typeof(Node)) + { + return node; + } + } + + // fetch node from server. + try + { + return await FetchNodeAsync(nodeId, ct).ConfigureAwait(false); + } + catch (Exception e) + { + Utils.LogError("Could not fetch node from server: NodeId={0}, Reason='{1}'.", nodeId, e.Message); + // m_nodes[nodeId] = null; + return null; + } + } + + /// + public async Task> FindAsync(IList nodeIds, CancellationToken ct = default) + { + // check for null. + if (nodeIds == null || nodeIds.Count == 0) + { + return new List(); + } + + int count = nodeIds.Count; + IList nodes = new List(count); + var fetchNodeIds = new ExpandedNodeIdCollection(); + + int ii; + for (ii = 0; ii < count; ii++) + { + INode node; + try + { + m_cacheLock.EnterReadLock(); + + // check if node already exists. + node = m_nodes.Find(nodeIds[ii]); + } + finally + { + m_cacheLock.ExitReadLock(); + } + + // do not return temporary nodes created after a Browse(). + if (node != null && + node?.GetType() != typeof(Node)) + { + nodes.Add(node); + } + else + { + nodes.Add(null); + fetchNodeIds.Add(nodeIds[ii]); + } + } + + if (fetchNodeIds.Count == 0) + { + return nodes; + } + + // fetch missing nodes from server. + IList fetchedNodes; + try + { + fetchedNodes = await FetchNodesAsync(fetchNodeIds, ct).ConfigureAwait(false); + } + catch (Exception e) + { + Utils.LogError("Could not fetch nodes from server: Reason='{0}'.", e.Message); + // m_nodes[nodeId] = null; + return nodes; + } + + ii = 0; + foreach (Node fetchedNode in fetchedNodes) + { + while (ii < count && nodes[ii] != null) + { + ii++; + } + if (ii < count && nodes[ii] == null) + { + nodes[ii++] = fetchedNode; + } + else + { + Utils.LogError("Inconsistency fetching nodes from server. Not all nodes could be assigned."); + break; + } + } + + return nodes; + } + + #region ITypeTable Methods + /// + public async Task FindSuperTypeAsync(ExpandedNodeId typeId, CancellationToken ct) + { + INode type = await FindAsync(typeId, ct).ConfigureAwait(false); + + if (type == null) + { + return null; + } + + try + { + m_cacheLock.EnterReadLock(); + + return m_typeTree.FindSuperType(typeId); + } + finally + { + m_cacheLock.ExitReadLock(); + } + } + + /// + public async Task FindSuperTypeAsync(NodeId typeId, CancellationToken ct = default) + { + INode type = await FindAsync(typeId, ct).ConfigureAwait(false); + + if (type == null) + { + return null; + } + + try + { + m_cacheLock.EnterReadLock(); + + return m_typeTree.FindSuperType(typeId); + } + finally + { + m_cacheLock.ExitReadLock(); + } + } + #endregion + + #region INodeCache Methods + /// + public async Task FetchNodeAsync(ExpandedNodeId nodeId, CancellationToken ct) + { + NodeId localId = ExpandedNodeId.ToNodeId(nodeId, m_session.NamespaceUris); + + if (localId == null) + { + return null; + } + + // fetch node from server. + Node source = await m_session.ReadNodeAsync(localId, ct).ConfigureAwait(false); + + try + { + // fetch references from server. + ReferenceDescriptionCollection references = await m_session.FetchReferencesAsync(localId, ct).ConfigureAwait(false); + + try + { + m_cacheLock.EnterUpgradeableReadLock(); + + foreach (ReferenceDescription reference in references) + { + // create a placeholder for the node if it does not already exist. + if (!m_nodes.Exists(reference.NodeId)) + { + // transform absolute identifiers. + if (reference.NodeId != null && reference.NodeId.IsAbsolute) + { + reference.NodeId = ExpandedNodeId.ToNodeId(reference.NodeId, NamespaceUris); + } + + Node target = new Node(reference); + + InternalWriteLockedAttach(target); + } + + // add the reference. + source.ReferenceTable.Add(reference.ReferenceTypeId, !reference.IsForward, reference.NodeId); + } + } + finally + { + m_cacheLock.ExitUpgradeableReadLock(); + } + } + catch (Exception e) + { + Utils.LogError("Could not fetch references for valid node with NodeId = {0}. Error = {1}", nodeId, e.Message); + } + + InternalWriteLockedAttach(source); + + return source; + } + + /// + public async Task> FetchNodesAsync(IList nodeIds, CancellationToken ct) + { + int count = nodeIds.Count; + if (count == 0) + { + return new List(); + } + + NodeIdCollection localIds = new NodeIdCollection( + nodeIds.Select(nodeId => ExpandedNodeId.ToNodeId(nodeId, m_session.NamespaceUris))); + + // fetch nodes and references from server. + (IList sourceNodes, IList readErrors) = await m_session.ReadNodesAsync(localIds, NodeClass.Unspecified, ct: ct).ConfigureAwait(false); + (IList referenceCollectionList, IList fetchErrors) = await m_session.FetchReferencesAsync(localIds, ct).ConfigureAwait(false); ; + + + int ii = 0; + for (ii = 0; ii < count; ii++) + { + if (ServiceResult.IsBad(readErrors[ii])) + { + continue; + } + + if (!ServiceResult.IsBad(fetchErrors[ii])) + { + // fetch references from server. + ReferenceDescriptionCollection references = referenceCollectionList[ii]; + + foreach (ReferenceDescription reference in references) + { + try + { + m_cacheLock.EnterUpgradeableReadLock(); + + // create a placeholder for the node if it does not already exist. + if (!m_nodes.Exists(reference.NodeId)) + { + // transform absolute identifiers. + if (reference.NodeId != null && reference.NodeId.IsAbsolute) + { + reference.NodeId = ExpandedNodeId.ToNodeId(reference.NodeId, NamespaceUris); + } + + Node target = new Node(reference); + + InternalWriteLockedAttach(target); + } + } + finally + { + m_cacheLock.ExitUpgradeableReadLock(); + } + + // add the reference. + sourceNodes[ii].ReferenceTable.Add(reference.ReferenceTypeId, !reference.IsForward, reference.NodeId); + } + } + + InternalWriteLockedAttach(sourceNodes[ii]); + } + + return sourceNodes; + } + + /// + public async Task> FindReferencesAsync( + ExpandedNodeId nodeId, + NodeId referenceTypeId, + bool isInverse, + bool includeSubtypes, + CancellationToken ct) + { + IList targets = new List(); + + Node source = await FindAsync(nodeId, ct).ConfigureAwait(false) as Node; + + if (source == null) + { + return targets; + } + + IList references; + try + { + m_cacheLock.EnterReadLock(); + + references = source.ReferenceTable.Find(referenceTypeId, isInverse, includeSubtypes, m_typeTree); + } + finally + { + m_cacheLock.ExitReadLock(); + } + + var targetIds = new ExpandedNodeIdCollection( + references.Select(reference => reference.TargetId)); + + IList result = await FindAsync(targetIds, ct).ConfigureAwait(false); + + foreach (INode target in result) + { + if (target != null) + { + targets.Add(target); + } + } + return targets; + } + + /// + public async Task> FindReferencesAsync( + IList nodeIds, + IList referenceTypeIds, + bool isInverse, + bool includeSubtypes, + CancellationToken ct) + { + IList targets = new List(); + if (nodeIds.Count == 0 || referenceTypeIds.Count == 0) + { + return targets; + } + ExpandedNodeIdCollection targetIds = new ExpandedNodeIdCollection(); + IList sources = await FindAsync(nodeIds, ct).ConfigureAwait(false); + foreach (INode source in sources) + { + if (!(source is Node node)) + { + continue; + } + + foreach (var referenceTypeId in referenceTypeIds) + { + IList references; + try + { + m_cacheLock.EnterReadLock(); + + references = node.ReferenceTable.Find(referenceTypeId, isInverse, includeSubtypes, m_typeTree); + } + finally + { + m_cacheLock.ExitReadLock(); + } + + targetIds.AddRange( + references.Select(reference => reference.TargetId)); + } + } + + IList result = await FindAsync(targetIds, ct).ConfigureAwait(false); + foreach (INode target in result) + { + if (target != null) + { + targets.Add(target); + } + } + + return targets; + } + + /// + public async Task FetchSuperTypesAsync(ExpandedNodeId nodeId, CancellationToken ct) + { + // find the target node, + ILocalNode source = await FindAsync(nodeId, ct).ConfigureAwait(false) as ILocalNode; + + if (source == null) + { + return; + } + + // follow the tree. + ILocalNode subType = source; + + while (subType != null) + { + ILocalNode superType = null; + + IList references = subType.References.Find(ReferenceTypeIds.HasSubtype, true, true, this); + + if (references != null && references.Count > 0) + { + superType = await FindAsync(references[0].TargetId, ct).ConfigureAwait(false) as ILocalNode; + } + + subType = superType; + } + } + #endregion + } +} +#endif diff --git a/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj b/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj index 10cf86589..a808f4a19 100644 --- a/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj +++ b/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj @@ -21,7 +21,7 @@ $(PackageId).Debug - + diff --git a/Libraries/Opc.Ua.Client/ReverseConnectManager.cs b/Libraries/Opc.Ua.Client/ReverseConnectManager.cs index 3d1246720..91441ab2a 100644 --- a/Libraries/Opc.Ua.Client/ReverseConnectManager.cs +++ b/Libraries/Opc.Ua.Client/ReverseConnectManager.cs @@ -189,7 +189,6 @@ public ReverseConnectManager() { m_state = ReverseConnectManagerState.New; m_registrations = new List(); - m_registrationsLock = new object(); m_endpointUrls = new Dictionary(); m_cts = new CancellationTokenSource(); } @@ -466,7 +465,7 @@ public void ClearWaitingConnections() public async Task WaitForConnection( Uri endpointUrl, string serverUri, - CancellationToken ct = default(CancellationToken)) + CancellationToken ct = default) { var tcs = new TaskCompletionSource(); int hashCode = RegisterWaitingConnection(endpointUrl, serverUri, @@ -474,7 +473,7 @@ public async Task WaitForConnection( ReverseConnectStrategy.Once); Func listenForCancelTaskFnc = async () => { - if (ct == default(CancellationToken)) + if (ct == default) { var waitTimeout = m_configuration.WaitTimeout > 0 ? m_configuration.WaitTimeout : DefaultWaitTimeout; await Task.Delay(waitTimeout).ConfigureAwait(false); diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs index 84c2bcad8..89c2cc336 100644 --- a/Libraries/Opc.Ua.Client/Session.cs +++ b/Libraries/Opc.Ua.Client/Session.cs @@ -44,7 +44,7 @@ namespace Opc.Ua.Client /// /// Manages a session with a server. /// - public partial class Session : SessionClientBatched, ISession, IDisposable + public partial class Session : SessionClientBatched, ISession { #region Constructors /// @@ -137,7 +137,7 @@ public Session(ITransportChannel channel, Session template, bool copyEventHandle foreach (Subscription subscription in template.Subscriptions) { - AddSubscription(new Subscription(subscription, copyEventHandlers)); + AddSubscription(subscription.CloneSubscription(copyEventHandlers)); } } #endregion @@ -374,12 +374,14 @@ protected override void Dispose(bool disposing) { if (disposing) { - Utils.SilentDispose(m_keepAliveTimer); - m_keepAliveTimer = null; + StopKeepAliveTimer(); Utils.SilentDispose(m_defaultSubscription); m_defaultSubscription = null; + Utils.SilentDispose(m_nodeCache); + m_nodeCache = null; + IList subscriptions = null; lock (SyncRoot) { @@ -683,12 +685,12 @@ public int SubscriptionCount } /// - /// If the subscriptions are deleted when a session is closed. + /// If the subscriptions are deleted when a session is closed. /// /// /// Default true, set to false if subscriptions need to /// be transferred or for durable subscriptions. - /// + /// public bool DeleteSubscriptionsOnClose { get { return m_deleteSubscriptionsOnClose; } @@ -696,12 +698,12 @@ public bool DeleteSubscriptionsOnClose } /// - /// If the subscriptions are transferred when a session is reconnected. + /// If the subscriptions are transferred when a session is reconnected. /// /// /// Default false, set to true if subscriptions should /// be transferred after reconnect. Service must be supported by server. - /// + /// public bool TransferSubscriptionsOnReconnect { get { return m_transferSubscriptionsOnReconnect; } @@ -920,7 +922,6 @@ public static Task Create( /// The certificate to use for the client. /// The list of available endpoints returned by server in GetEndpoints() response. /// The value of profileUris used in GetEndpoints() request. - /// public static Session Create( ApplicationConfiguration configuration, ITransportChannel channel, @@ -929,7 +930,30 @@ public static Session Create( EndpointDescriptionCollection availableEndpoints = null, StringCollection discoveryProfileUris = null) { - return new Session(channel, configuration, endpoint, clientCertificate, availableEndpoints, discoveryProfileUris); + return Create(DefaultSessionFactory.Instance, configuration, channel, endpoint, clientCertificate, availableEndpoints, discoveryProfileUris); + } + + /// + /// Creates a new session with a server using the specified channel by invoking the CreateSession service. + /// With the sessionInstantiator subclasses of Sessions can be created. + /// + /// The Session constructor to use to create the session. + /// The configuration for the client application. + /// The channel for the server. + /// The endpoint for the server. + /// The certificate to use for the client. + /// The list of available endpoints returned by server in GetEndpoints() response. + /// The value of profileUris used in GetEndpoints() request. + public static Session Create( + ISessionInstantiator sessionInstantiator, + ApplicationConfiguration configuration, + ITransportChannel channel, + ConfiguredEndpoint endpoint, + X509Certificate2 clientCertificate, + EndpointDescriptionCollection availableEndpoints = null, + StringCollection discoveryProfileUris = null) + { + return sessionInstantiator.Create(channel, configuration, endpoint, clientCertificate, availableEndpoints, discoveryProfileUris); } /// @@ -937,7 +961,7 @@ public static Session Create( /// /// The application configuration. /// The client endpoint for the reverse connect. - /// A configured endpoint to connect to. + /// A configured endpoint to connect to. /// Update configuration based on server prior connect. /// Check that the certificate specifies a valid domain (computer) name. /// The cancellation token. @@ -1033,7 +1057,38 @@ public static async Task CreateChannelAsync( /// The preferred locales. /// The cancellation token. /// The new session object. + public static Task Create( + ApplicationConfiguration configuration, + ITransportWaitingConnection connection, + ConfiguredEndpoint endpoint, + bool updateBeforeConnect, + bool checkDomain, + string sessionName, + uint sessionTimeout, + IUserIdentity identity, + IList preferredLocales, + CancellationToken ct = default) + { + return Create(DefaultSessionFactory.Instance, configuration, connection, endpoint, updateBeforeConnect, checkDomain, sessionName, sessionTimeout, identity, preferredLocales, ct); + } + + /// + /// Creates a new communication session with a server using a reverse connection. + /// + /// The Session constructor to use to create the session. + /// The configuration for the client application. + /// The client endpoint for the reverse connect. + /// The endpoint for the server. + /// If set to true the discovery endpoint is used to update the endpoint description before connecting. + /// If set to true then the domain in the certificate must match the endpoint used. + /// The name to assign to the session. + /// The timeout period for the session. + /// The user identity to associate with the session. + /// The preferred locales. + /// The cancellation token. + /// The new session object. public static async Task Create( + ISessionInstantiator sessionInstantiator, ApplicationConfiguration configuration, ITransportWaitingConnection connection, ConfiguredEndpoint endpoint, @@ -1049,12 +1104,12 @@ public static async Task Create( ITransportChannel channel = await Session.CreateChannelAsync(configuration, connection, endpoint, updateBeforeConnect, checkDomain, ct).ConfigureAwait(false); // create the session object. - Session session = new Session(channel, configuration, endpoint, null); + Session session = sessionInstantiator.Create(channel, configuration, endpoint, null); // create the session. try { - session.Open(sessionName, sessionTimeout, identity, preferredLocales, checkDomain); + await session.OpenAsync(sessionName, sessionTimeout, identity, preferredLocales, checkDomain, ct).ConfigureAwait(false); } catch (Exception) { @@ -1079,7 +1134,39 @@ public static async Task Create( /// The preferred locales. /// The cancellation token. /// The new session object. + public static Task Create( + ApplicationConfiguration configuration, + ReverseConnectManager reverseConnectManager, + ConfiguredEndpoint endpoint, + bool updateBeforeConnect, + bool checkDomain, + string sessionName, + uint sessionTimeout, + IUserIdentity userIdentity, + IList preferredLocales, + CancellationToken ct = default + ) + { + return Create(DefaultSessionFactory.Instance, configuration, reverseConnectManager, endpoint, updateBeforeConnect, checkDomain, sessionName, sessionTimeout, userIdentity, preferredLocales, ct); + } + + /// + /// Creates a new communication session with a server using a reverse connect manager. + /// + /// The Session constructor to use to create the session. + /// The configuration for the client application. + /// The reverse connect manager for the client connection. + /// The endpoint for the server. + /// If set to true the discovery endpoint is used to update the endpoint description before connecting. + /// If set to true then the domain in the certificate must match the endpoint used. + /// The name to assign to the session. + /// The timeout period for the session. + /// The user identity to associate with the session. + /// The preferred locales. + /// The cancellation token. + /// The new session object. public static async Task Create( + ISessionInstantiator sessionInstantiator, ApplicationConfiguration configuration, ReverseConnectManager reverseConnectManager, ConfiguredEndpoint endpoint, @@ -1094,8 +1181,7 @@ public static async Task Create( { if (reverseConnectManager == null) { - return await Create(configuration, endpoint, updateBeforeConnect, - checkDomain, sessionName, sessionTimeout, userIdentity, preferredLocales).ConfigureAwait(false); + return await Create(sessionInstantiator, configuration, (ITransportWaitingConnection)null, endpoint, updateBeforeConnect, checkDomain, sessionName, sessionTimeout, userIdentity, preferredLocales, ct).ConfigureAwait(false); } ITransportWaitingConnection connection = null; @@ -1111,13 +1197,15 @@ public static async Task Create( await endpoint.UpdateFromServerAsync( endpoint.EndpointUrl, connection, endpoint.Description.SecurityMode, - endpoint.Description.SecurityPolicyUri).ConfigureAwait(false); + endpoint.Description.SecurityPolicyUri, + ct).ConfigureAwait(false); updateBeforeConnect = false; connection = null; } } while (connection == null); return await Create( + sessionInstantiator, configuration, connection, endpoint, @@ -1151,7 +1239,7 @@ public static Session Recreate(Session template) messageContext); // create the session object. - Session session = new Session(channel, template, true); + Session session = template.CloneSession(channel, true); try { @@ -1197,7 +1285,7 @@ public static Session Recreate(Session template, ITransportWaitingConnection con messageContext); // create the session object. - Session session = new Session(channel, template, true); + Session session = template.CloneSession(channel, true); try { @@ -1232,7 +1320,7 @@ public static Session Recreate(Session template, ITransportChannel transportChan messageContext.Factory = template.Factory; // create the session object. - Session session = new Session(transportChannel, template, true); + Session session = template.CloneSession(transportChannel, true); try { @@ -1297,9 +1385,7 @@ public SessionConfiguration SaveSessionConfiguration(Stream stream = null) XmlWriterSettings settings = Utils.DefaultXmlWriterSettings(); using (XmlWriter writer = XmlWriter.Create(stream, settings)) { - DataContractSerializer serializer = new DataContractSerializer(typeof(SessionConfiguration), - new[] { typeof(UserIdentityToken), typeof(AnonymousIdentityToken), typeof(X509IdentityToken), - typeof(IssuedIdentityToken), typeof(UserIdentity) }); + DataContractSerializer serializer = new DataContractSerializer(typeof(SessionConfiguration)); serializer.WriteObject(writer, sessionConfiguration); } } @@ -1308,9 +1394,7 @@ public SessionConfiguration SaveSessionConfiguration(Stream stream = null) /// public void Reconnect() - { - Reconnect(null, null); - } + => Reconnect(null, null); /// public void Reconnect(ITransportWaitingConnection connection) @@ -1328,130 +1412,31 @@ private void Reconnect(ITransportWaitingConnection connection, ITransportChannel bool resetReconnect = false; try { - // check if already connecting. - if (m_reconnecting) - { - Utils.LogWarning("Session is already attempting to reconnect."); - - throw ServiceResultException.Create( - StatusCodes.BadInvalidState, - "Session is already attempting to reconnect."); - } - - Utils.LogInfo("Session RECONNECT starting."); + Utils.LogInfo("Session RECONNECT {0} starting.", SessionId); m_reconnectLock.Wait(); + bool reconnecting = m_reconnecting; m_reconnecting = true; resetReconnect = true; m_reconnectLock.Release(); - lock (SyncRoot) - { - // stop keep alives. - Utils.SilentDispose(m_keepAliveTimer); - m_keepAliveTimer = null; - } - - // create the client signature. - byte[] dataToSign = Utils.Append(m_serverCertificate != null ? m_serverCertificate.RawData : null, m_serverNonce); - EndpointDescription endpoint = m_endpoint.Description; - SignatureData clientSignature = SecurityPolicies.Sign(m_instanceCertificate, endpoint.SecurityPolicyUri, dataToSign); - - // check that the user identity is supported by the endpoint. - UserTokenPolicy identityPolicy = endpoint.FindUserTokenPolicy(m_identity.TokenType, m_identity.IssuedTokenType); - - if (identityPolicy == null) + // check if already connecting. + if (reconnecting) { - Utils.LogError("Reconnect: Endpoint does not support the user identity type provided."); + Utils.LogWarning("Session is already attempting to reconnect."); throw ServiceResultException.Create( - StatusCodes.BadUserAccessDenied, - "Endpoint does not support the user identity type provided."); - } - - // select the security policy for the user token. - string securityPolicyUri = identityPolicy.SecurityPolicyUri; - - if (String.IsNullOrEmpty(securityPolicyUri)) - { - securityPolicyUri = endpoint.SecurityPolicyUri; - } - - // need to refresh the identity (reprompt for password, refresh token). - if (m_RenewUserIdentity != null) - { - m_identity = m_RenewUserIdentity(this, m_identity); + StatusCodes.BadInvalidState, + "Session is already attempting to reconnect."); } - // validate server nonce and security parameters for user identity. - ValidateServerNonce( - m_identity, - m_serverNonce, - securityPolicyUri, - m_previousServerNonce, - m_endpoint.Description.SecurityMode); - - // sign data with user token. - UserIdentityToken identityToken = m_identity.GetIdentityToken(); - identityToken.PolicyId = identityPolicy.PolicyId; - SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); - - // encrypt token. - identityToken.Encrypt(m_serverCertificate, m_serverNonce, securityPolicyUri); - - // send the software certificates assigned to the client. - SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates(); - - Utils.LogInfo("Session REPLACING channel."); + IAsyncResult result = PrepareReconnectBeginActivate( + connection, + transportChannel); - if (connection != null) - { - // check if the channel supports reconnect. - if ((TransportChannel.SupportedFeatures & TransportChannelFeatures.Reconnect) != 0) - { - TransportChannel.Reconnect(connection); - } - else - { - // initialize the channel which will be created with the server. - ITransportChannel channel = SessionChannel.Create( - m_configuration, - connection, - m_endpoint.Description, - m_endpoint.Configuration, - m_instanceCertificate, - m_configuration.SecurityConfiguration.SendCertificateChain ? m_instanceCertificateChain : null, - MessageContext); - - // disposes the existing channel. - TransportChannel = channel; - } - } - else if (transportChannel != null) - { - TransportChannel = transportChannel; - } - else + if (!result.AsyncWaitHandle.WaitOne(kReconnectTimeout / 2)) { - // check if the channel supports reconnect. - if (TransportChannel != null && (TransportChannel.SupportedFeatures & TransportChannelFeatures.Reconnect) != 0) - { - TransportChannel.Reconnect(); - } - else - { - // initialize the channel which will be created with the server. - ITransportChannel channel = SessionChannel.Create( - m_configuration, - m_endpoint.Description, - m_endpoint.Configuration, - m_instanceCertificate, - m_configuration.SecurityConfiguration.SendCertificateChain ? m_instanceCertificateChain : null, - MessageContext); - - // disposes the existing channel. - TransportChannel = channel; - } + Utils.LogWarning("WARNING: ACTIVATE SESSION timed out. {0}/{1}", GoodPublishRequestCount, OutstandingRequestCount); } // reactivate session. @@ -1459,24 +1444,6 @@ private void Reconnect(ITransportWaitingConnection connection, ITransportChannel StatusCodeCollection certificateResults = null; DiagnosticInfoCollection certificateDiagnosticInfos = null; - Utils.LogInfo("Session RE-ACTIVATING session."); - - RequestHeader header = new RequestHeader() { TimeoutHint = kReconnectTimeout }; - IAsyncResult result = BeginActivateSession( - header, - clientSignature, - null, - m_preferredLocales, - new ExtensionObject(identityToken), - userTokenSignature, - null, - null); - - if (!result.AsyncWaitHandle.WaitOne(kReconnectTimeout / 2)) - { - Utils.LogWarning("WARNING: ACTIVATE SESSION timed out. {0}/{1}", GoodPublishRequestCount, OutstandingRequestCount); - } - EndActivateSession( result, out serverNonce, @@ -1485,9 +1452,10 @@ private void Reconnect(ITransportWaitingConnection connection, ITransportChannel int publishCount = 0; + Utils.LogInfo("Session RECONNECT {0} completed successfully.", SessionId); + lock (SyncRoot) { - Utils.LogInfo("Session RECONNECT completed successfully."); m_previousServerNonce = m_serverNonce; m_serverNonce = serverNonce; publishCount = GetMinPublishRequestCount(true); @@ -1520,35 +1488,35 @@ private void Reconnect(ITransportWaitingConnection connection, ITransportChannel } /// - public void Save(string filePath) + public void Save(string filePath, IEnumerable knownTypes = null) { - Save(filePath, Subscriptions); + Save(filePath, Subscriptions, knownTypes); } /// - public void Save(Stream stream, IEnumerable subscriptions) + public void Save(Stream stream, IEnumerable subscriptions, IEnumerable knownTypes = null) { SubscriptionCollection subscriptionList = new SubscriptionCollection(subscriptions); XmlWriterSettings settings = Utils.DefaultXmlWriterSettings(); using (XmlWriter writer = XmlWriter.Create(stream, settings)) { - DataContractSerializer serializer = new DataContractSerializer(typeof(SubscriptionCollection)); + DataContractSerializer serializer = new DataContractSerializer(typeof(SubscriptionCollection), knownTypes); serializer.WriteObject(writer, subscriptionList); } } /// - public void Save(string filePath, IEnumerable subscriptions) + public void Save(string filePath, IEnumerable subscriptions, IEnumerable knownTypes = null) { using (FileStream stream = new FileStream(filePath, FileMode.Create)) { - Save(stream, subscriptions); + Save(stream, subscriptions, knownTypes); } } /// - public IEnumerable Load(Stream stream, bool transferSubscriptions = false) + public IEnumerable Load(Stream stream, bool transferSubscriptions = false, IEnumerable knownTypes = null) { // secure settings XmlReaderSettings settings = Utils.DefaultXmlReaderSettings(); @@ -1556,7 +1524,7 @@ public IEnumerable Load(Stream stream, bool transferSubscriptions using (XmlReader reader = XmlReader.Create(stream, settings)) { - DataContractSerializer serializer = new DataContractSerializer(typeof(SubscriptionCollection)); + DataContractSerializer serializer = new DataContractSerializer(typeof(SubscriptionCollection), knownTypes); SubscriptionCollection subscriptions = (SubscriptionCollection)serializer.ReadObject(reader); foreach (Subscription subscription in subscriptions) { @@ -1576,37 +1544,21 @@ public IEnumerable Load(Stream stream, bool transferSubscriptions } /// - public IEnumerable Load(string filePath, bool transferSubscriptions = false) + public IEnumerable Load(string filePath, bool transferSubscriptions = false, IEnumerable knownTypes = null) { using (FileStream stream = File.OpenRead(filePath)) { - return Load(stream, transferSubscriptions); + return Load(stream, transferSubscriptions, knownTypes); } } /// public void FetchNamespaceTables() { - ReadValueIdCollection nodesToRead = new ReadValueIdCollection(); - - // request namespace array. - ReadValueId valueId = new ReadValueId { - NodeId = Variables.Server_NamespaceArray, - AttributeId = Attributes.Value - }; - - nodesToRead.Add(valueId); - - // request server array. - valueId = new ReadValueId { - NodeId = Variables.Server_ServerArray, - AttributeId = Attributes.Value - }; - - nodesToRead.Add(valueId); + ReadValueIdCollection nodesToRead = PrepareNamespaceTableNodesToRead(); // read from server. - ResponseHeader responseHeader = this.Read( + ResponseHeader responseHeader = base.Read( null, 0, TimestampsToReturn.Neither, @@ -1617,29 +1569,7 @@ public void FetchNamespaceTables() ValidateResponse(values, nodesToRead); ValidateDiagnosticInfos(diagnosticInfos, nodesToRead); - // validate namespace array. - ServiceResult result = ValidateDataValue(values[0], typeof(string[]), 0, diagnosticInfos, responseHeader); - - if (ServiceResult.IsBad(result)) - { - Utils.LogError("FetchNamespaceTables: Cannot read NamespaceArray node: {0}", result.StatusCode); - } - else - { - m_namespaceUris.Update((string[])values[0].Value); - } - - // validate server array. - result = ValidateDataValue(values[1], typeof(string[]), 1, diagnosticInfos, responseHeader); - - if (ServiceResult.IsBad(result)) - { - Utils.LogError("FetchNamespaceTables: Cannot read ServerArray node: {0} ", result.StatusCode); - } - else - { - m_serverUris.Update((string[])values[1].Value); - } + UpdateNamespaceTable(values, diagnosticInfos, responseHeader); } /// @@ -1815,7 +1745,7 @@ public ReferenceDescription FindDataDescription(NodeId encodingId) } /// - public async Task FindDataDictionary(NodeId descriptionId) + public async Task FindDataDictionary(NodeId descriptionId, CancellationToken ct = default) { // check if the dictionary has already been loaded. foreach (DataDictionary dictionary in m_dictionaries.Values) @@ -1826,7 +1756,7 @@ public async Task FindDataDictionary(NodeId descriptionId) } } - IList references = this.NodeCache.FindReferences(descriptionId, ReferenceTypeIds.HasComponent, true, false); + IList references = await NodeCache.FindReferencesAsync(descriptionId, ReferenceTypeIds.HasComponent, true, false, ct).ConfigureAwait(false); if (references.Count == 0) { throw ServiceResultException.Create(StatusCodes.BadNodeIdInvalid, "Description does not refer to a valid data dictionary."); @@ -1837,7 +1767,7 @@ public async Task FindDataDictionary(NodeId descriptionId) DataDictionary dictionaryToLoad = new DataDictionary(this); - await dictionaryToLoad.Load(references[0]).ConfigureAwait(false); + dictionaryToLoad.Load(references[0]); m_dictionaries[dictionaryId] = dictionaryToLoad; @@ -1845,7 +1775,7 @@ public async Task FindDataDictionary(NodeId descriptionId) } /// - public async Task LoadDataDictionary(ReferenceDescription dictionaryNode, bool forceReload = false) + public DataDictionary LoadDataDictionary(ReferenceDescription dictionaryNode, bool forceReload = false) { // check if the dictionary has already been loaded. DataDictionary dictionary; @@ -1858,13 +1788,13 @@ public async Task LoadDataDictionary(ReferenceDescription dictio // load the dictionary. DataDictionary dictionaryToLoad = new DataDictionary(this); - await dictionaryToLoad.Load(dictionaryId, dictionaryNode.ToString()).ConfigureAwait(false); + dictionaryToLoad.Load(dictionaryId, dictionaryNode.ToString()); m_dictionaries[dictionaryId] = dictionaryToLoad; return dictionaryToLoad; } /// - public async Task> LoadDataTypeSystem(NodeId dataTypeSystem = null) + public async Task> LoadDataTypeSystem(NodeId dataTypeSystem = null, CancellationToken ct = default) { if (dataTypeSystem == null) { @@ -1897,7 +1827,7 @@ public async Task> LoadDataTypeSystem(NodeId var referenceExpandedNodeIds = references .Select(r => ExpandedNodeId.ToNodeId(r.NodeId, this.NamespaceUris)) .Where(n => n.NamespaceIndex != 0).ToList(); - IDictionary schemas = await DataDictionary.ReadDictionaries(this, referenceExpandedNodeIds).ConfigureAwait(false); + IDictionary schemas = await DataDictionary.ReadDictionaries(this, referenceExpandedNodeIds, ct).ConfigureAwait(false); // read namespace property values var namespaces = new Dictionary(); @@ -1908,7 +1838,11 @@ public async Task> LoadDataTypeSystem(NodeId { if (StatusCode.IsNotBad(errors[ii].StatusCode)) { - namespaces[((NodeId)referenceNodeIds[ii])] = (string)nameSpaceValues[ii]; + // servers may optimize space by not returning a dictionary + if (nameSpaceValues[ii] != null) + { + namespaces[((NodeId)referenceNodeIds[ii])] = (string)nameSpaceValues[ii]; + } } else { @@ -1940,11 +1874,11 @@ public async Task> LoadDataTypeSystem(NodeId dictionaryToLoad = new DataDictionary(this); if (schemas.TryGetValue(dictionaryId, out var schema)) { - await dictionaryToLoad.Load(dictionaryId, dictionaryId.ToString(), schema, imports).ConfigureAwait(false); + dictionaryToLoad.Load(dictionaryId, dictionaryId.ToString(), schema, imports); } else { - await dictionaryToLoad.Load(dictionaryId, dictionaryId.ToString()).ConfigureAwait(false); + dictionaryToLoad.Load(dictionaryId, dictionaryId.ToString()); } m_dictionaries[dictionaryId] = dictionaryToLoad; } @@ -2382,59 +2316,7 @@ public void Open( IList preferredLocales, bool checkDomain) { - // check connection state. - lock (SyncRoot) - { - if (Connected) - { - throw new ServiceResultException(StatusCodes.BadInvalidState, "Already connected to server."); - } - } - - string securityPolicyUri = m_endpoint.Description.SecurityPolicyUri; - - // catch security policies which are not supported by core - if (SecurityPolicies.GetDisplayName(securityPolicyUri) == null) - { - throw ServiceResultException.Create( - StatusCodes.BadSecurityChecksFailed, - "The chosen security policy is not supported by the client to connect to the server."); - } - - // get the identity token. - if (identity == null) - { - identity = new UserIdentity(); - } - - // get identity token. - UserIdentityToken identityToken = identity.GetIdentityToken(); - - // check that the user identity is supported by the endpoint. - UserTokenPolicy identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identityToken.PolicyId); - - if (identityPolicy == null) - { - // try looking up by TokenType if the policy id was not found. - identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identity.TokenType, identity.IssuedTokenType); - - if (identityPolicy == null) - { - throw ServiceResultException.Create( - StatusCodes.BadUserAccessDenied, - "Endpoint does not support the user identity type provided."); - } - - identityToken.PolicyId = identityPolicy.PolicyId; - } - - bool requireEncryption = securityPolicyUri != SecurityPolicies.None; - - if (!requireEncryption) - { - requireEncryption = identityPolicy.SecurityPolicyUri != SecurityPolicies.None && - !String.IsNullOrEmpty(identityPolicy.SecurityPolicyUri); - } + OpenValidateIdentity(ref identity, out var identityToken, out var identityPolicy, out string securityPolicyUri, out bool requireEncryption); // validate the server certificate /certificate chain. X509Certificate2 serverCertificate = null; @@ -2476,27 +2358,14 @@ public void Open( SignedSoftwareCertificateCollection serverSoftwareCertificates = null; // send the application instance certificate for the client. - byte[] clientCertificateData = m_instanceCertificate != null ? m_instanceCertificate.RawData : null; - byte[] clientCertificateChainData = null; + BuildCertificateData(out byte[] clientCertificateData, out byte[] clientCertificateChainData); - if (m_instanceCertificateChain != null && m_instanceCertificateChain.Count > 0 && m_configuration.SecurityConfiguration.SendCertificateChain) - { - List clientCertificateChain = new List(); - - for (int i = 0; i < m_instanceCertificateChain.Count; i++) - { - clientCertificateChain.AddRange(m_instanceCertificateChain[i].RawData); - } - - clientCertificateChainData = clientCertificateChain.ToArray(); - } - - ApplicationDescription clientDescription = new ApplicationDescription(); - - clientDescription.ApplicationUri = m_configuration.ApplicationUri; - clientDescription.ApplicationName = m_configuration.ApplicationName; - clientDescription.ApplicationType = ApplicationType.Client; - clientDescription.ProductUri = m_configuration.ProductUri; + ApplicationDescription clientDescription = new ApplicationDescription { + ApplicationUri = m_configuration.ApplicationUri, + ApplicationName = m_configuration.ApplicationName, + ApplicationType = ApplicationType.Client, + ProductUri = m_configuration.ProductUri + }; if (sessionTimeout == 0) { @@ -2561,6 +2430,7 @@ public void Open( out serverSignature, out m_maxRequestMessageSize); } + // save session id. lock (SyncRoot) { @@ -2575,200 +2445,16 @@ public void Open( try { // verify that the server returned the same instance certificate. - if (serverCertificateData != null && - m_endpoint.Description.ServerCertificate != null && - !Utils.IsEqual(serverCertificateData, m_endpoint.Description.ServerCertificate)) - { - try - { - // verify for certificate chain in endpoint. - X509Certificate2Collection serverCertificateChain = Utils.ParseCertificateChainBlob(m_endpoint.Description.ServerCertificate); - - if (serverCertificateChain.Count > 0 && !Utils.IsEqual(serverCertificateData, serverCertificateChain[0].RawData)) - { - throw ServiceResultException.Create( - StatusCodes.BadCertificateInvalid, - "Server did not return the certificate used to create the secure channel."); - } - } - catch (Exception) - { - throw ServiceResultException.Create( - StatusCodes.BadCertificateInvalid, - "Server did not return the certificate used to create the secure channel."); - } - } - - if (serverSignature == null || serverSignature.Signature == null) - { - Utils.LogInfo("Server signature is null or empty."); - - //throw ServiceResultException.Create( - // StatusCodes.BadSecurityChecksFailed, - // "Server signature is null or empty."); - } - - if (m_discoveryServerEndpoints != null && m_discoveryServerEndpoints.Count > 0) - { - // Compare EndpointDescriptions returned at GetEndpoints with values returned at CreateSession - EndpointDescriptionCollection expectedServerEndpoints = null; - - if (serverEndpoints != null && - m_discoveryProfileUris != null && m_discoveryProfileUris.Count > 0) - { - // Select EndpointDescriptions with a transportProfileUri that matches the - // profileUris specified in the original GetEndpoints() request. - expectedServerEndpoints = new EndpointDescriptionCollection(); - - foreach (EndpointDescription serverEndpoint in serverEndpoints) - { - if (m_discoveryProfileUris.Contains(serverEndpoint.TransportProfileUri)) - { - expectedServerEndpoints.Add(serverEndpoint); - } - } - } - else - { - expectedServerEndpoints = serverEndpoints; - } - - if (expectedServerEndpoints == null || - m_discoveryServerEndpoints.Count != expectedServerEndpoints.Count) - { - throw ServiceResultException.Create( - StatusCodes.BadSecurityChecksFailed, - "Server did not return a number of ServerEndpoints that matches the one from GetEndpoints."); - } - - for (int ii = 0; ii < expectedServerEndpoints.Count; ii++) - { - EndpointDescription serverEndpoint = expectedServerEndpoints[ii]; - EndpointDescription expectedServerEndpoint = m_discoveryServerEndpoints[ii]; - - if (serverEndpoint.SecurityMode != expectedServerEndpoint.SecurityMode || - serverEndpoint.SecurityPolicyUri != expectedServerEndpoint.SecurityPolicyUri || - serverEndpoint.TransportProfileUri != expectedServerEndpoint.TransportProfileUri || - serverEndpoint.SecurityLevel != expectedServerEndpoint.SecurityLevel) - { - throw ServiceResultException.Create( - StatusCodes.BadSecurityChecksFailed, - "The list of ServerEndpoints returned at CreateSession does not match the list from GetEndpoints."); - } - - if (serverEndpoint.UserIdentityTokens.Count != expectedServerEndpoint.UserIdentityTokens.Count) - { - throw ServiceResultException.Create( - StatusCodes.BadSecurityChecksFailed, - "The list of ServerEndpoints returned at CreateSession does not match the one from GetEndpoints."); - } + ValidateServerCertificateData(serverCertificateData); - for (int jj = 0; jj < serverEndpoint.UserIdentityTokens.Count; jj++) - { - if (!serverEndpoint.UserIdentityTokens[jj].IsEqual(expectedServerEndpoint.UserIdentityTokens[jj])) - { - throw ServiceResultException.Create( - StatusCodes.BadSecurityChecksFailed, - "The list of ServerEndpoints returned at CreateSession does not match the one from GetEndpoints."); - } - } - } - } + ValidateServerEndpoints(serverEndpoints); - // find the matching description (TBD - check domains against certificate). - bool found = false; - Uri expectedUrl = Utils.ParseUri(m_endpoint.Description.EndpointUrl); + ValidateServerSignature(serverCertificate, serverSignature, clientCertificateData, clientCertificateChainData, clientNonce); - if (expectedUrl != null) - { - for (int ii = 0; ii < serverEndpoints.Count; ii++) - { - EndpointDescription serverEndpoint = serverEndpoints[ii]; - Uri actualUrl = Utils.ParseUri(serverEndpoint.EndpointUrl); - - if (actualUrl != null && actualUrl.Scheme == expectedUrl.Scheme) - { - if (serverEndpoint.SecurityPolicyUri == m_endpoint.Description.SecurityPolicyUri) - { - if (serverEndpoint.SecurityMode == m_endpoint.Description.SecurityMode) - { - // ensure endpoint has up to date information. - m_endpoint.Description.Server.ApplicationName = serverEndpoint.Server.ApplicationName; - m_endpoint.Description.Server.ApplicationUri = serverEndpoint.Server.ApplicationUri; - m_endpoint.Description.Server.ApplicationType = serverEndpoint.Server.ApplicationType; - m_endpoint.Description.Server.ProductUri = serverEndpoint.Server.ProductUri; - m_endpoint.Description.TransportProfileUri = serverEndpoint.TransportProfileUri; - m_endpoint.Description.UserIdentityTokens = serverEndpoint.UserIdentityTokens; - - found = true; - break; - } - } - } - } - } - - // could be a security risk. - if (!found) - { - throw ServiceResultException.Create( - StatusCodes.BadSecurityChecksFailed, - "Server did not return an EndpointDescription that matched the one used to create the secure channel."); - } - - // validate the server's signature. - byte[] dataToSign = Utils.Append(clientCertificateData, clientNonce); - - if (!SecurityPolicies.Verify(serverCertificate, m_endpoint.Description.SecurityPolicyUri, dataToSign, serverSignature)) - { - // validate the signature with complete chain if the check with leaf certificate failed. - if (clientCertificateChainData != null) - { - dataToSign = Utils.Append(clientCertificateChainData, clientNonce); - - if (!SecurityPolicies.Verify(serverCertificate, m_endpoint.Description.SecurityPolicyUri, dataToSign, serverSignature)) - { - throw ServiceResultException.Create( - StatusCodes.BadApplicationSignatureInvalid, - "Server did not provide a correct signature for the nonce data provided by the client."); - } - } - else - { - throw ServiceResultException.Create( - StatusCodes.BadApplicationSignatureInvalid, - "Server did not provide a correct signature for the nonce data provided by the client."); - } - } - - // get a validator to check certificates provided by server. - CertificateValidator validator = m_configuration.CertificateValidator; - - // validate software certificates. - List softwareCertificates = new List(); - - foreach (SignedSoftwareCertificate signedCertificate in serverSoftwareCertificates) - { - SoftwareCertificate softwareCertificate = null; - - ServiceResult result = SoftwareCertificate.Validate( - validator, - signedCertificate.CertificateData, - out softwareCertificate); - - if (ServiceResult.IsBad(result)) - { - OnSoftwareCertificateError(signedCertificate, result); - } - - softwareCertificates.Add(softwareCertificate); - } - - // check if software certificates meet application requirements. - ValidateSoftwareCertificates(softwareCertificates); + HandleSignedSoftwareCertificates(serverSoftwareCertificates); // create the client signature. - dataToSign = Utils.Append(serverCertificate != null ? serverCertificate.RawData : null, serverNonce); + byte[] dataToSign = Utils.Append(serverCertificate != null ? serverCertificate.RawData : null, serverNonce); SignatureData clientSignature = SecurityPolicies.Sign(m_instanceCertificate, securityPolicyUri, dataToSign); // select the security policy for the user token. @@ -3260,6 +2946,38 @@ public void ReadDisplayName( } } } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) return true; + + if (obj is ISession session) + { + if (!m_endpoint.Equals(session.Endpoint)) return false; + if (!m_sessionName.Equals(session.SessionName, StringComparison.Ordinal)) return false; + if (!SessionId.Equals(session.SessionId)) return false; + + return true; + } + + return false; + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(m_endpoint, m_sessionName, SessionId); + } + + /// + /// An overrideable version of a session clone which is used + /// internally to create new subclassed clones from a Session class. + /// + public virtual Session CloneSession(ITransportChannel channel, bool copyEventHandlers) + { + return new Session(channel, this, copyEventHandlers); + } #endregion #region Close Methods @@ -3291,8 +3009,7 @@ public virtual StatusCode Close(int timeout, bool closeChannel) StatusCode result = StatusCodes.Good; // stop the keep alive timer. - Utils.SilentDispose(m_keepAliveTimer); - m_keepAliveTimer = null; + StopKeepAliveTimer(); // check if currectly connected. bool connected = Connected; @@ -3337,9 +3054,9 @@ public virtual StatusCode Close(int timeout, bool closeChannel) { // dont throw errors on disconnect, but return them // so the caller can log the error. - if (e is ServiceResultException) + if (e is ServiceResultException sre) { - result = ((ServiceResultException)e).StatusCode; + result = sre.StatusCode; } else { @@ -4027,8 +3744,7 @@ private void StartKeepAliveTimer() // restart the publish timer. lock (SyncRoot) { - Utils.SilentDispose(m_keepAliveTimer); - m_keepAliveTimer = null; + StopKeepAliveTimer(); // start timer. m_keepAliveTimer = new Timer(OnKeepAlive, nodesToRead, keepAliveInterval, keepAliveInterval); @@ -4038,6 +3754,15 @@ private void StartKeepAliveTimer() OnKeepAlive(nodesToRead); } + /// + /// Stops the keep alive timer. + /// + private void StopKeepAliveTimer() + { + Utils.SilentDispose(m_keepAliveTimer); + m_keepAliveTimer = null; + } + /// /// Removes a completed async request. /// @@ -4370,6 +4095,62 @@ private void CreateNodeClassAttributesReadNodesRequest( } } + /// + /// Prepares the list of node ids to read to fetch the namespace table. + /// + private ReadValueIdCollection PrepareNamespaceTableNodesToRead() + { + var nodesToRead = new ReadValueIdCollection(); + + // request namespace array. + ReadValueId valueId = new ReadValueId { + NodeId = Variables.Server_NamespaceArray, + AttributeId = Attributes.Value + }; + + nodesToRead.Add(valueId); + + // request server array. + valueId = new ReadValueId { + NodeId = Variables.Server_ServerArray, + AttributeId = Attributes.Value + }; + + nodesToRead.Add(valueId); + + return nodesToRead; + } + + /// + /// Updates the NamespaceTable with the result of the read operation. + /// + private void UpdateNamespaceTable(DataValueCollection values, DiagnosticInfoCollection diagnosticInfos, ResponseHeader responseHeader) + { + // validate namespace array. + ServiceResult result = ValidateDataValue(values[0], typeof(string[]), 0, diagnosticInfos, responseHeader); + + if (ServiceResult.IsBad(result)) + { + Utils.LogError("FetchNamespaceTables: Cannot read NamespaceArray node: {0}", result.StatusCode); + } + else + { + m_namespaceUris.Update((string[])values[0].Value); + } + + // validate server array. + result = ValidateDataValue(values[1], typeof(string[]), 1, diagnosticInfos, responseHeader); + + if (ServiceResult.IsBad(result)) + { + Utils.LogError("FetchNamespaceTables: Cannot read ServerArray node: {0} ", result.StatusCode); + } + else + { + m_serverUris.Update((string[])values[1].Value); + } + } + /// /// Creates a read request with attributes determined by the NodeClass. /// @@ -4429,7 +4210,7 @@ bool optionalAttributes } /// - /// Builds the node collection results based on the attribute values of the read response. + /// Builds the node collection results based on the attribute values of the read response. /// /// The collection of all attributes to read passed in the read request. /// The attributes requested per NodeId @@ -5283,6 +5064,8 @@ private void OnPublishComplete(IAsyncResult result) case StatusCodes.BadSessionIdInvalid: case StatusCodes.BadSecureChannelIdInvalid: case StatusCodes.BadSecureChannelClosed: + case StatusCodes.BadSecurityChecksFailed: + case StatusCodes.BadCertificateInvalid: case StatusCodes.BadServerHalted: return; @@ -5348,59 +5131,7 @@ public bool Republish(uint subscriptionId, uint sequenceNumber) } catch (Exception e) { - ServiceResult error = new ServiceResult(e); - - bool result = true; - switch (error.StatusCode.Code) - { - case StatusCodes.BadMessageNotAvailable: - Utils.LogWarning("Message {0}-{1} no longer available.", subscriptionId, sequenceNumber); - break; - // if encoding limits are exceeded, the issue is logged and - // the published data is acknowledged to prevent the endless republish loop. - case StatusCodes.BadEncodingLimitsExceeded: - Utils.LogError(e, "Message {0}-{1} exceeded size limits, ignored.", subscriptionId, sequenceNumber); - var ack = new SubscriptionAcknowledgement { - SubscriptionId = subscriptionId, - SequenceNumber = sequenceNumber - }; - lock (SyncRoot) - { - m_acknowledgementsToSend.Add(ack); - } - break; - default: - result = false; - Utils.LogError(e, "Unexpected error sending republish request."); - break; - } - - PublishErrorEventHandler callback = null; - - lock (m_eventLock) - { - callback = m_PublishError; - } - - // raise an error event. - if (callback != null) - { - try - { - PublishErrorEventArgs args = new PublishErrorEventArgs( - error, - subscriptionId, - sequenceNumber); - - callback(this, args); - } - catch (Exception e2) - { - Utils.LogError(e2, "Session: Unexpected error invoking PublishErrorCallback."); - } - } - - return result; + return ProcessRepublishResponseError(e, subscriptionId, sequenceNumber); } } @@ -5448,6 +5179,506 @@ public bool ResendData(IEnumerable subscriptions, out IList + /// Validates the identity for an open call. + /// + private void OpenValidateIdentity( + ref IUserIdentity identity, + out UserIdentityToken identityToken, + out UserTokenPolicy identityPolicy, + out string securityPolicyUri, + out bool requireEncryption) + { + // check connection state. + lock (SyncRoot) + { + if (Connected) + { + throw new ServiceResultException(StatusCodes.BadInvalidState, "Already connected to server."); + } + } + + securityPolicyUri = m_endpoint.Description.SecurityPolicyUri; + + // catch security policies which are not supported by core + if (SecurityPolicies.GetDisplayName(securityPolicyUri) == null) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + "The chosen security policy is not supported by the client to connect to the server."); + } + + // get the identity token. + if (identity == null) + { + identity = new UserIdentity(); + } + + // get identity token. + identityToken = identity.GetIdentityToken(); + + // check that the user identity is supported by the endpoint. + identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identityToken.PolicyId); + + if (identityPolicy == null) + { + // try looking up by TokenType if the policy id was not found. + identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identity.TokenType, identity.IssuedTokenType); + + if (identityPolicy == null) + { + throw ServiceResultException.Create( + StatusCodes.BadUserAccessDenied, + "Endpoint does not support the user identity type provided."); + } + + identityToken.PolicyId = identityPolicy.PolicyId; + } + + requireEncryption = securityPolicyUri != SecurityPolicies.None; + + if (!requireEncryption) + { + requireEncryption = identityPolicy.SecurityPolicyUri != SecurityPolicies.None && + !String.IsNullOrEmpty(identityPolicy.SecurityPolicyUri); + } + } + + private void BuildCertificateData(out byte[] clientCertificateData, out byte[] clientCertificateChainData) + { + // send the application instance certificate for the client. + clientCertificateData = m_instanceCertificate != null ? m_instanceCertificate.RawData : null; + clientCertificateChainData = null; + + if (m_instanceCertificateChain != null && m_instanceCertificateChain.Count > 0 && + m_configuration.SecurityConfiguration.SendCertificateChain) + { + List clientCertificateChain = new List(); + + for (int i = 0; i < m_instanceCertificateChain.Count; i++) + { + clientCertificateChain.AddRange(m_instanceCertificateChain[i].RawData); + } + + clientCertificateChainData = clientCertificateChain.ToArray(); + } + } + + /// + /// Validates the server certificate returned. + /// + private void ValidateServerCertificateData(byte[] serverCertificateData) + { + if (serverCertificateData != null && + m_endpoint.Description.ServerCertificate != null && + !Utils.IsEqual(serverCertificateData, m_endpoint.Description.ServerCertificate)) + { + try + { + // verify for certificate chain in endpoint. + X509Certificate2Collection serverCertificateChain = Utils.ParseCertificateChainBlob(m_endpoint.Description.ServerCertificate); + + if (serverCertificateChain.Count > 0 && !Utils.IsEqual(serverCertificateData, serverCertificateChain[0].RawData)) + { + throw ServiceResultException.Create( + StatusCodes.BadCertificateInvalid, + "Server did not return the certificate used to create the secure channel."); + } + } + catch (Exception) + { + throw ServiceResultException.Create( + StatusCodes.BadCertificateInvalid, + "Server did not return the certificate used to create the secure channel."); + } + } + } + + /// + /// Validates the server signature created with the client nonce. + /// + private void ValidateServerSignature(X509Certificate2 serverCertificate, SignatureData serverSignature, + byte[] clientCertificateData, byte[] clientCertificateChainData, byte[] clientNonce) + { + if (serverSignature == null || serverSignature.Signature == null) + { + Utils.LogInfo("Server signature is null or empty."); + + //throw ServiceResultException.Create( + // StatusCodes.BadSecurityChecksFailed, + // "Server signature is null or empty."); + } + + // validate the server's signature. + byte[] dataToSign = Utils.Append(clientCertificateData, clientNonce); + + if (!SecurityPolicies.Verify(serverCertificate, m_endpoint.Description.SecurityPolicyUri, dataToSign, serverSignature)) + { + // validate the signature with complete chain if the check with leaf certificate failed. + if (clientCertificateChainData != null) + { + dataToSign = Utils.Append(clientCertificateChainData, clientNonce); + + if (!SecurityPolicies.Verify(serverCertificate, m_endpoint.Description.SecurityPolicyUri, dataToSign, serverSignature)) + { + throw ServiceResultException.Create( + StatusCodes.BadApplicationSignatureInvalid, + "Server did not provide a correct signature for the nonce data provided by the client."); + } + } + else + { + throw ServiceResultException.Create( + StatusCodes.BadApplicationSignatureInvalid, + "Server did not provide a correct signature for the nonce data provided by the client."); + } + } + } + + /// + /// Validates the server endpoints returned. + /// + private void ValidateServerEndpoints(EndpointDescriptionCollection serverEndpoints) + { + if (m_discoveryServerEndpoints != null && m_discoveryServerEndpoints.Count > 0) + { + // Compare EndpointDescriptions returned at GetEndpoints with values returned at CreateSession + EndpointDescriptionCollection expectedServerEndpoints = null; + + if (serverEndpoints != null && + m_discoveryProfileUris != null && m_discoveryProfileUris.Count > 0) + { + // Select EndpointDescriptions with a transportProfileUri that matches the + // profileUris specified in the original GetEndpoints() request. + expectedServerEndpoints = new EndpointDescriptionCollection(); + + foreach (EndpointDescription serverEndpoint in serverEndpoints) + { + if (m_discoveryProfileUris.Contains(serverEndpoint.TransportProfileUri)) + { + expectedServerEndpoints.Add(serverEndpoint); + } + } + } + else + { + expectedServerEndpoints = serverEndpoints; + } + + if (expectedServerEndpoints == null || + m_discoveryServerEndpoints.Count != expectedServerEndpoints.Count) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + "Server did not return a number of ServerEndpoints that matches the one from GetEndpoints."); + } + + for (int ii = 0; ii < expectedServerEndpoints.Count; ii++) + { + EndpointDescription serverEndpoint = expectedServerEndpoints[ii]; + EndpointDescription expectedServerEndpoint = m_discoveryServerEndpoints[ii]; + + if (serverEndpoint.SecurityMode != expectedServerEndpoint.SecurityMode || + serverEndpoint.SecurityPolicyUri != expectedServerEndpoint.SecurityPolicyUri || + serverEndpoint.TransportProfileUri != expectedServerEndpoint.TransportProfileUri || + serverEndpoint.SecurityLevel != expectedServerEndpoint.SecurityLevel) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + "The list of ServerEndpoints returned at CreateSession does not match the list from GetEndpoints."); + } + + if (serverEndpoint.UserIdentityTokens.Count != expectedServerEndpoint.UserIdentityTokens.Count) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + "The list of ServerEndpoints returned at CreateSession does not match the one from GetEndpoints."); + } + + for (int jj = 0; jj < serverEndpoint.UserIdentityTokens.Count; jj++) + { + if (!serverEndpoint.UserIdentityTokens[jj].IsEqual(expectedServerEndpoint.UserIdentityTokens[jj])) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + "The list of ServerEndpoints returned at CreateSession does not match the one from GetEndpoints."); + } + } + } + } + + // find the matching description (TBD - check domains against certificate). + bool found = false; + Uri expectedUrl = Utils.ParseUri(m_endpoint.Description.EndpointUrl); + + if (expectedUrl != null) + { + for (int ii = 0; ii < serverEndpoints.Count; ii++) + { + EndpointDescription serverEndpoint = serverEndpoints[ii]; + Uri actualUrl = Utils.ParseUri(serverEndpoint.EndpointUrl); + + if (actualUrl != null && actualUrl.Scheme == expectedUrl.Scheme) + { + if (serverEndpoint.SecurityPolicyUri == m_endpoint.Description.SecurityPolicyUri) + { + if (serverEndpoint.SecurityMode == m_endpoint.Description.SecurityMode) + { + // ensure endpoint has up to date information. + m_endpoint.Description.Server.ApplicationName = serverEndpoint.Server.ApplicationName; + m_endpoint.Description.Server.ApplicationUri = serverEndpoint.Server.ApplicationUri; + m_endpoint.Description.Server.ApplicationType = serverEndpoint.Server.ApplicationType; + m_endpoint.Description.Server.ProductUri = serverEndpoint.Server.ProductUri; + m_endpoint.Description.TransportProfileUri = serverEndpoint.TransportProfileUri; + m_endpoint.Description.UserIdentityTokens = serverEndpoint.UserIdentityTokens; + + found = true; + break; + } + } + } + } + } + + // could be a security risk. + if (!found) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + "Server did not return an EndpointDescription that matched the one used to create the secure channel."); + } + } + + /// + /// Helper to prepare the reconnect channel + /// and signature data before activate. + /// + private IAsyncResult PrepareReconnectBeginActivate( + ITransportWaitingConnection connection, + ITransportChannel transportChannel + ) + { + Utils.LogInfo("Session RECONNECT {0} starting.", SessionId); + + lock (SyncRoot) + { + // stop keep alives. + StopKeepAliveTimer(); + } + + // create the client signature. + byte[] dataToSign = Utils.Append(m_serverCertificate != null ? m_serverCertificate.RawData : null, m_serverNonce); + EndpointDescription endpoint = m_endpoint.Description; + SignatureData clientSignature = SecurityPolicies.Sign(m_instanceCertificate, endpoint.SecurityPolicyUri, dataToSign); + + // check that the user identity is supported by the endpoint. + UserTokenPolicy identityPolicy = endpoint.FindUserTokenPolicy(m_identity.TokenType, m_identity.IssuedTokenType); + + if (identityPolicy == null) + { + Utils.LogError("Reconnect: Endpoint does not support the user identity type provided."); + + throw ServiceResultException.Create( + StatusCodes.BadUserAccessDenied, + "Endpoint does not support the user identity type provided."); + } + + // select the security policy for the user token. + string securityPolicyUri = identityPolicy.SecurityPolicyUri; + + if (String.IsNullOrEmpty(securityPolicyUri)) + { + securityPolicyUri = endpoint.SecurityPolicyUri; + } + + // need to refresh the identity (reprompt for password, refresh token). + if (m_RenewUserIdentity != null) + { + m_identity = m_RenewUserIdentity(this, m_identity); + } + + // validate server nonce and security parameters for user identity. + ValidateServerNonce( + m_identity, + m_serverNonce, + securityPolicyUri, + m_previousServerNonce, + m_endpoint.Description.SecurityMode); + + // sign data with user token. + UserIdentityToken identityToken = m_identity.GetIdentityToken(); + identityToken.PolicyId = identityPolicy.PolicyId; + SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); + + // encrypt token. + identityToken.Encrypt(m_serverCertificate, m_serverNonce, securityPolicyUri); + + // send the software certificates assigned to the client. + SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates(); + + Utils.LogInfo("Session REPLACING channel for {0}.", SessionId); + + if (connection != null) + { + // check if the channel supports reconnect. + if ((TransportChannel.SupportedFeatures & TransportChannelFeatures.Reconnect) != 0) + { + TransportChannel.Reconnect(connection); + } + else + { + // initialize the channel which will be created with the server. + ITransportChannel channel = SessionChannel.Create( + m_configuration, + connection, + m_endpoint.Description, + m_endpoint.Configuration, + m_instanceCertificate, + m_configuration.SecurityConfiguration.SendCertificateChain ? m_instanceCertificateChain : null, + MessageContext); + + // disposes the existing channel. + TransportChannel = channel; + } + } + else if (transportChannel != null) + { + TransportChannel = transportChannel; + } + else + { + // check if the channel supports reconnect. + if (TransportChannel != null && (TransportChannel.SupportedFeatures & TransportChannelFeatures.Reconnect) != 0) + { + TransportChannel.Reconnect(); + } + else + { + // initialize the channel which will be created with the server. + ITransportChannel channel = SessionChannel.Create( + m_configuration, + m_endpoint.Description, + m_endpoint.Configuration, + m_instanceCertificate, + m_configuration.SecurityConfiguration.SendCertificateChain ? m_instanceCertificateChain : null, + MessageContext); + + // disposes the existing channel. + TransportChannel = channel; + } + } + + Utils.LogInfo("Session RE-ACTIVATING {0}.", SessionId); + + RequestHeader header = new RequestHeader() { TimeoutHint = kReconnectTimeout }; + return BeginActivateSession( + header, + clientSignature, + null, + m_preferredLocales, + new ExtensionObject(identityToken), + userTokenSignature, + null, + null); + } + + /// + /// Process Republish error response. + /// + /// The exception that occurred during the republish operation. + /// The subscription Id for which the republish was requested. + /// The sequencenumber for which the republish was requested. + private bool ProcessRepublishResponseError(Exception e, uint subscriptionId, uint sequenceNumber) + { + + ServiceResult error = new ServiceResult(e); + + bool result = true; + switch (error.StatusCode.Code) + { + case StatusCodes.BadMessageNotAvailable: + Utils.LogWarning("Message {0}-{1} no longer available.", subscriptionId, sequenceNumber); + break; + + // if encoding limits are exceeded, the issue is logged and + // the published data is acknowledged to prevent the endless republish loop. + case StatusCodes.BadEncodingLimitsExceeded: + Utils.LogError(e, "Message {0}-{1} exceeded size limits, ignored.", subscriptionId, sequenceNumber); + var ack = new SubscriptionAcknowledgement { + SubscriptionId = subscriptionId, + SequenceNumber = sequenceNumber + }; + lock (SyncRoot) + { + m_acknowledgementsToSend.Add(ack); + } + break; + default: + result = false; + Utils.LogError(e, "Unexpected error sending republish request."); + break; + } + + PublishErrorEventHandler callback = null; + + lock (m_eventLock) + { + callback = m_PublishError; + } + + // raise an error event. + if (callback != null) + { + try + { + PublishErrorEventArgs args = new PublishErrorEventArgs( + error, + subscriptionId, + sequenceNumber); + + callback(this, args); + } + catch (Exception e2) + { + Utils.LogError(e2, "Session: Unexpected error invoking PublishErrorCallback."); + } + } + + return result; + } + + /// + /// Handles the validation of server software certificates and application callback. + /// + private void HandleSignedSoftwareCertificates(SignedSoftwareCertificateCollection serverSoftwareCertificates) + { + // get a validator to check certificates provided by server. + CertificateValidator validator = m_configuration.CertificateValidator; + + // validate software certificates. + List softwareCertificates = new List(); + + foreach (SignedSoftwareCertificate signedCertificate in serverSoftwareCertificates) + { + SoftwareCertificate softwareCertificate = null; + + ServiceResult result = SoftwareCertificate.Validate( + validator, + signedCertificate.CertificateData, + out softwareCertificate); + + if (ServiceResult.IsBad(result)) + { + OnSoftwareCertificateError(signedCertificate, result); + } + + softwareCertificates.Add(softwareCertificate); + } + + // check if software certificates meet application requirements. + ValidateSoftwareCertificates(softwareCertificates); + } + /// /// Processes the response from a publish request. /// @@ -5498,7 +5729,7 @@ private void ProcessPublishResponse( } #if DEBUG_SEQUENTIALPUBLISHING - // Checks for debug info only. + // Checks for debug info only. // Once more than a single publish request is queued, the checks are invalid // because a publish response may not include the latest ack information yet. @@ -5571,13 +5802,13 @@ private void ProcessPublishResponse( // Validate publish time and reject old values. if (notificationMessage.PublishTime.AddMilliseconds(subscription.CurrentPublishingInterval * subscription.CurrentLifetimeCount) < DateTime.UtcNow) { - Utils.LogWarning("PublishTime {0} in publish response is too old for SubscriptionId {1}.", notificationMessage.PublishTime.ToLocalTime(), subscription.Id); + Utils.LogTrace("PublishTime {0} in publish response is too old for SubscriptionId {1}.", notificationMessage.PublishTime.ToLocalTime(), subscription.Id); } // Validate publish time and reject old values. if (notificationMessage.PublishTime > DateTime.UtcNow.AddMilliseconds(subscription.CurrentPublishingInterval * subscription.CurrentLifetimeCount)) { - Utils.LogWarning("PublishTime {0} in publish response is newer than actual time for SubscriptionId {1}.", notificationMessage.PublishTime.ToLocalTime(), subscription.Id); + Utils.LogTrace("PublishTime {0} in publish response is newer than actual time for SubscriptionId {1}.", notificationMessage.PublishTime.ToLocalTime(), subscription.Id); } // update subscription cache. diff --git a/Libraries/Opc.Ua.Client/SessionAsync.cs b/Libraries/Opc.Ua.Client/SessionAsync.cs index c2b25bfc7..d5ebf6e3f 100644 --- a/Libraries/Opc.Ua.Client/SessionAsync.cs +++ b/Libraries/Opc.Ua.Client/SessionAsync.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -32,8 +32,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; +using Opc.Ua.Bindings; namespace Opc.Ua.Client { @@ -41,8 +44,282 @@ namespace Opc.Ua.Client /// Manages a session with a server. /// Contains the async versions of the public session api. /// - public partial class Session : SessionClientBatched, ISession, IDisposable + public partial class Session : SessionClientBatched, ISession { + #region Open Async Methods + /// + public Task OpenAsync( + string sessionName, + IUserIdentity identity, + CancellationToken ct) + { + return OpenAsync(sessionName, 0, identity, null, ct); + } + + /// + public Task OpenAsync( + string sessionName, + uint sessionTimeout, + IUserIdentity identity, + IList preferredLocales, + CancellationToken ct) + { + return OpenAsync(sessionName, sessionTimeout, identity, preferredLocales, true, ct); + } + + /// + public async Task OpenAsync( + string sessionName, + uint sessionTimeout, + IUserIdentity identity, + IList preferredLocales, + bool checkDomain, + CancellationToken ct) + { + OpenValidateIdentity(ref identity, out var identityToken, out var identityPolicy, out string securityPolicyUri, out bool requireEncryption); + + // validate the server certificate /certificate chain. + X509Certificate2 serverCertificate = null; + byte[] certificateData = m_endpoint.Description.ServerCertificate; + + if (certificateData != null && certificateData.Length > 0) + { + X509Certificate2Collection serverCertificateChain = Utils.ParseCertificateChainBlob(certificateData); + + if (serverCertificateChain.Count > 0) + { + serverCertificate = serverCertificateChain[0]; + } + + if (requireEncryption) + { + if (checkDomain) + { + await m_configuration.CertificateValidator.ValidateAsync(serverCertificateChain, m_endpoint, ct).ConfigureAwait(false); + } + else + { + await m_configuration.CertificateValidator.ValidateAsync(serverCertificateChain, ct).ConfigureAwait(false); + } + // save for reconnect + m_checkDomain = checkDomain; + } + } + + // create a nonce. + uint length = (uint)m_configuration.SecurityConfiguration.NonceLength; + byte[] clientNonce = Utils.Nonce.CreateNonce(length); + + // send the application instance certificate for the client. + BuildCertificateData(out byte[] clientCertificateData, out byte[] clientCertificateChainData); + + ApplicationDescription clientDescription = new ApplicationDescription { + ApplicationUri = m_configuration.ApplicationUri, + ApplicationName = m_configuration.ApplicationName, + ApplicationType = ApplicationType.Client, + ProductUri = m_configuration.ProductUri + }; + + if (sessionTimeout == 0) + { + sessionTimeout = (uint)m_configuration.ClientConfiguration.DefaultSessionTimeout; + } + + bool successCreateSession = false; + CreateSessionResponse response = null; + + //if security none, first try to connect without certificate + if (m_endpoint.Description.SecurityPolicyUri == SecurityPolicies.None) + { + //first try to connect with client certificate NULL + try + { + response = await base.CreateSessionAsync( + null, + clientDescription, + m_endpoint.Description.Server.ApplicationUri, + m_endpoint.EndpointUrl.ToString(), + sessionName, + clientNonce, + null, + sessionTimeout, + (uint)MessageContext.MaxMessageSize, + ct).ConfigureAwait(false); + + successCreateSession = true; + } + catch (Exception ex) + { + Utils.LogInfo("Create session failed with client certificate NULL. " + ex.Message); + successCreateSession = false; + } + } + + if (!successCreateSession) + { + response = await base.CreateSessionAsync( + null, + clientDescription, + m_endpoint.Description.Server.ApplicationUri, + m_endpoint.EndpointUrl.ToString(), + sessionName, + clientNonce, + clientCertificateChainData != null ? clientCertificateChainData : clientCertificateData, + sessionTimeout, + (uint)MessageContext.MaxMessageSize, + ct).ConfigureAwait(false); + } + + NodeId sessionId = response.SessionId; + NodeId sessionCookie = response.AuthenticationToken; + byte[] serverNonce = response.ServerNonce; + byte[] serverCertificateData = response.ServerCertificate; + SignatureData serverSignature = response.ServerSignature; + EndpointDescriptionCollection serverEndpoints = response.ServerEndpoints; + SignedSoftwareCertificateCollection serverSoftwareCertificates = response.ServerSoftwareCertificates; + + m_sessionTimeout = response.RevisedSessionTimeout; + m_maxRequestMessageSize = response.MaxRequestMessageSize; + + // save session id. + lock (SyncRoot) + { + base.SessionCreated(sessionId, sessionCookie); + } + + Utils.LogInfo("Revised session timeout value: {0}. ", m_sessionTimeout); + Utils.LogInfo("Max response message size value: {0}. Max request message size: {1} ", + MessageContext.MaxMessageSize, m_maxRequestMessageSize); + + //we need to call CloseSession if CreateSession was successful but some other exception is thrown + try + { + // verify that the server returned the same instance certificate. + ValidateServerCertificateData(serverCertificateData); + + ValidateServerEndpoints(serverEndpoints); + + ValidateServerSignature(serverCertificate, serverSignature, clientCertificateData, clientCertificateChainData, clientNonce); + + HandleSignedSoftwareCertificates(serverSoftwareCertificates); + + // create the client signature. + byte[] dataToSign = Utils.Append(serverCertificate != null ? serverCertificate.RawData : null, serverNonce); + SignatureData clientSignature = SecurityPolicies.Sign(m_instanceCertificate, securityPolicyUri, dataToSign); + + // select the security policy for the user token. + securityPolicyUri = identityPolicy.SecurityPolicyUri; + + if (String.IsNullOrEmpty(securityPolicyUri)) + { + securityPolicyUri = m_endpoint.Description.SecurityPolicyUri; + } + + byte[] previousServerNonce = null; + + if (TransportChannel.CurrentToken != null) + { + previousServerNonce = TransportChannel.CurrentToken.ServerNonce; + } + + // validate server nonce and security parameters for user identity. + ValidateServerNonce( + identity, + serverNonce, + securityPolicyUri, + previousServerNonce, + m_endpoint.Description.SecurityMode); + + // sign data with user token. + SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); + + // encrypt token. + identityToken.Encrypt(serverCertificate, serverNonce, securityPolicyUri); + + // send the software certificates assigned to the client. + SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates(); + + // copy the preferred locales if provided. + if (preferredLocales != null && preferredLocales.Count > 0) + { + m_preferredLocales = new StringCollection(preferredLocales); + } + + // activate session. + ActivateSessionResponse activateResponse = await ActivateSessionAsync( + null, + clientSignature, + clientSoftwareCertificates, + m_preferredLocales, + new ExtensionObject(identityToken), + userTokenSignature, + ct).ConfigureAwait(false); + + serverNonce = activateResponse.ServerNonce; + StatusCodeCollection certificateResults = activateResponse.Results; + DiagnosticInfoCollection certificateDiagnosticInfos = activateResponse.DiagnosticInfos; + + if (certificateResults != null) + { + for (int i = 0; i < certificateResults.Count; i++) + { + Utils.LogInfo("ActivateSession result[{0}] = {1}", i, certificateResults[i]); + } + } + + if (certificateResults == null || certificateResults.Count == 0) + { + Utils.LogInfo("Empty results were received for the ActivateSession call."); + } + + // fetch namespaces. + await FetchNamespaceTablesAsync(ct).ConfigureAwait(false); + + lock (SyncRoot) + { + // save nonces. + m_sessionName = sessionName; + m_identity = identity; + m_previousServerNonce = previousServerNonce; + m_serverNonce = serverNonce; + m_serverCertificate = serverCertificate; + + // update system context. + m_systemContext.PreferredLocales = m_preferredLocales; + m_systemContext.SessionId = this.SessionId; + m_systemContext.UserIdentity = identity; + } + + // fetch operation limits + await FetchOperationLimitsAsync(ct).ConfigureAwait(false); + + // start keep alive thread. + StartKeepAliveTimer(); + + // raise event that session configuration chnaged. + IndicateSessionConfigurationChanged(); + } + catch (Exception) + { + try + { + await base.CloseSessionAsync(null, false, ct).ConfigureAwait(false); + CloseChannel(); + } + catch (Exception e) + { + Utils.LogError("Cleanup: CloseSession() or CloseChannel() raised exception. " + e.Message); + } + finally + { + SessionCreated(null, null); + } + + throw; + } + } + #endregion + #region Subscription Async Methods /// public async Task RemoveSubscriptionAsync(Subscription subscription, CancellationToken ct = default) @@ -108,9 +385,9 @@ public async Task ReactivateSubscriptionsAsync( if (subscriptionIds.Count > 0) { + await m_reconnectLock.WaitAsync(ct).ConfigureAwait(false); try { - await m_reconnectLock.WaitAsync().ConfigureAwait(false); m_reconnecting = true; for (int ii = 0; ii < subscriptions.Count; ii++) @@ -176,7 +453,7 @@ public async Task ReactivateSubscriptionsAsync( ClientBase.ValidateDiagnosticInfos(diagnosticInfos, requests); int ii = 0; - foreach (var value in results) + foreach (CallMethodResult value in results) { ServiceResult result = ServiceResult.Good; if (StatusCode.IsNotGood(value.StatusCode)) @@ -208,9 +485,9 @@ public async Task TransferSubscriptionsAsync( if (subscriptionIds.Count > 0) { + await m_reconnectLock.WaitAsync(ct).ConfigureAwait(false); try { - await m_reconnectLock.WaitAsync().ConfigureAwait(false); m_reconnecting = true; TransferSubscriptionsResponse response = await base.TransferSubscriptionsAsync(null, subscriptionIds, sendInitialValues, ct).ConfigureAwait(false); @@ -231,12 +508,12 @@ public async Task TransferSubscriptionsAsync( { if (StatusCode.IsGood(results[ii].StatusCode)) { - if (await subscriptions[ii].TransferAsync(this, subscriptionIds[ii], results[ii].AvailableSequenceNumbers).ConfigureAwait(false)) + if (await subscriptions[ii].TransferAsync(this, subscriptionIds[ii], results[ii].AvailableSequenceNumbers, ct).ConfigureAwait(false)) { lock (SyncRoot) { // create ack for available sequence numbers - foreach (var sequenceNumber in results[ii].AvailableSequenceNumbers) + foreach (uint sequenceNumber in results[ii].AvailableSequenceNumbers) { var ack = new SubscriptionAcknowledgement() { SubscriptionId = subscriptionIds[ii], @@ -278,6 +555,132 @@ public async Task TransferSubscriptionsAsync( } #endregion + #region FetchNamespaceTables Async Methods + /// + public async Task FetchNamespaceTablesAsync(CancellationToken ct = default) + { + ReadValueIdCollection nodesToRead = PrepareNamespaceTableNodesToRead(); + + // read from server. + ReadResponse response = await ReadAsync( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + ct).ConfigureAwait(false); + + DataValueCollection values = response.Results; + DiagnosticInfoCollection diagnosticInfos = response.DiagnosticInfos; + ResponseHeader responseHeader = response.ResponseHeader; + + ValidateResponse(values, nodesToRead); + ValidateDiagnosticInfos(diagnosticInfos, nodesToRead); + + UpdateNamespaceTable(values, diagnosticInfos, responseHeader); + } + #endregion + + #region FetchTypeTree Async Methods + /// + public async Task FetchTypeTreeAsync(ExpandedNodeId typeId, CancellationToken ct = default) + { + Node node = await NodeCache.FindAsync(typeId, ct).ConfigureAwait(false) as Node; + + if (node != null) + { + var subTypes = new ExpandedNodeIdCollection(); + foreach (IReference reference in node.Find(ReferenceTypeIds.HasSubtype, false)) + { + subTypes.Add(reference.TargetId); + } + if (subTypes.Count > 0) + { + await FetchTypeTreeAsync(subTypes, ct).ConfigureAwait(false); + } + } + } + + /// + public async Task FetchTypeTreeAsync(ExpandedNodeIdCollection typeIds, CancellationToken ct = default) + { + var referenceTypeIds = new NodeIdCollection() { ReferenceTypeIds.HasSubtype }; + IList nodes = await NodeCache.FindReferencesAsync(typeIds, referenceTypeIds, false, false, ct).ConfigureAwait(false); + var subTypes = new ExpandedNodeIdCollection(); + foreach (INode inode in nodes) + { + if (inode is Node node) + { + foreach (IReference reference in node.Find(ReferenceTypeIds.HasSubtype, false)) + { + if (!typeIds.Contains(reference.TargetId)) + { + subTypes.Add(reference.TargetId); + } + } + } + } + if (subTypes.Count > 0) + { + await FetchTypeTreeAsync(subTypes, ct).ConfigureAwait(false); + } + } + #endregion + + #region FetchOperationLimits Async Methods + /// + /// Fetch the operation limits of the server. + /// + public async Task FetchOperationLimitsAsync(CancellationToken ct) + { + try + { + var operationLimitsProperties = typeof(OperationLimits) + .GetProperties().Select(p => p.Name).ToList(); + + var nodeIds = new NodeIdCollection( + operationLimitsProperties.Select(name => (NodeId)typeof(VariableIds) + .GetField("Server_ServerCapabilities_OperationLimits_" + name, BindingFlags.Public | BindingFlags.Static) + .GetValue(null)) + ); + + (DataValueCollection values, IList errors) = await ReadValuesAsync(nodeIds, ct).ConfigureAwait(false); + + OperationLimits configOperationLimits = m_configuration?.ClientConfiguration?.OperationLimits ?? new OperationLimits(); + var operationLimits = new OperationLimits(); + + for (int ii = 0; ii < nodeIds.Count; ii++) + { + PropertyInfo property = typeof(OperationLimits).GetProperty(operationLimitsProperties[ii]); + uint value = (uint)property.GetValue(configOperationLimits); + if (values[ii] != null && + ServiceResult.IsNotBad(errors[ii])) + { + if (values[ii].Value is uint serverValue) + { + if (serverValue > 0 && + (value == 0 || serverValue < value)) + { + value = serverValue; + } + } + } + property.SetValue(operationLimits, value); + } + + OperationLimits = operationLimits; + } + catch (Exception ex) + { + Utils.LogError(ex, "Failed to read operation limits from server. Using configuration defaults."); + OperationLimits operationLimits = m_configuration?.ClientConfiguration?.OperationLimits; + if (operationLimits != null) + { + OperationLimits = operationLimits; + } + } + } + #endregion + #region ReadNode Async Methods /// public async Task<(IList, IList)> ReadNodesAsync( @@ -417,7 +820,7 @@ public async Task ReadNodeAsync( CancellationToken ct = default) { // build list of attributes. - var attributes = CreateAttributes(nodeClass, optionalAttributes); + IDictionary attributes = CreateAttributes(nodeClass, optionalAttributes); // build list of values to read. ReadValueIdCollection itemsToRead = new ReadValueIdCollection(); @@ -517,7 +920,7 @@ public async Task ReadValueAsync( ClientBase.ValidateResponse(values, itemsToRead); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, itemsToRead); - foreach (var value in values) + foreach (DataValue value in values) { ServiceResult result = ServiceResult.Good; if (StatusCode.IsBad(value.StatusCode)) @@ -531,6 +934,128 @@ public async Task ReadValueAsync( } #endregion + #region Browse Methods + /// + public async Task<( + ResponseHeader responseHeader, + ByteStringCollection continuationPoints, + IList referencesList, + IList errors + )> BrowseAsync( + RequestHeader requestHeader, + ViewDescription view, + IList nodesToBrowse, + uint maxResultsToReturn, + BrowseDirection browseDirection, + NodeId referenceTypeId, + bool includeSubtypes, + uint nodeClassMask, + CancellationToken ct = default) + { + + BrowseDescriptionCollection browseDescription = new BrowseDescriptionCollection(); + foreach (NodeId nodeToBrowse in nodesToBrowse) + { + BrowseDescription description = new BrowseDescription { + NodeId = nodeToBrowse, + BrowseDirection = browseDirection, + ReferenceTypeId = referenceTypeId, + IncludeSubtypes = includeSubtypes, + NodeClassMask = nodeClassMask, + ResultMask = (uint)BrowseResultMask.All + }; + + browseDescription.Add(description); + } + + BrowseResponse browseResponse = await BrowseAsync( + requestHeader, + view, + maxResultsToReturn, + browseDescription, + ct).ConfigureAwait(false); + + ClientBase.ValidateResponse(browseResponse.ResponseHeader); + BrowseResultCollection results = browseResponse.Results; + DiagnosticInfoCollection diagnosticInfos = browseResponse.DiagnosticInfos; + + ClientBase.ValidateResponse(results, browseDescription); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, browseDescription); + + int ii = 0; + var errors = new List(); + var continuationPoints = new ByteStringCollection(); + var referencesList = new List(); + foreach (BrowseResult result in results) + { + if (StatusCode.IsBad(result.StatusCode)) + { + errors.Add(new ServiceResult(result.StatusCode, ii, diagnosticInfos, browseResponse.ResponseHeader.StringTable)); + } + else + { + errors.Add(ServiceResult.Good); + } + continuationPoints.Add(result.ContinuationPoint); + referencesList.Add(result.References); + ii++; + } + + return (browseResponse.ResponseHeader, continuationPoints, referencesList, errors); + } + #endregion + + #region BrowseNext Methods + + /// + public async Task<( + ResponseHeader responseHeader, + ByteStringCollection revisedContinuationPoints, + IList referencesList, + List errors + )> BrowseNextAsync( + RequestHeader requestHeader, + ByteStringCollection continuationPoints, + bool releaseContinuationPoint, + CancellationToken ct = default) + { + BrowseNextResponse response = await base.BrowseNextAsync( + requestHeader, + releaseContinuationPoint, + continuationPoints, + ct).ConfigureAwait(false); + + ClientBase.ValidateResponse(response.ResponseHeader); + + BrowseResultCollection results = response.Results; + DiagnosticInfoCollection diagnosticInfos = response.DiagnosticInfos; + + ClientBase.ValidateResponse(results, continuationPoints); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints); + + int ii = 0; + var errors = new List(); + var revisedContinuationPoints = new ByteStringCollection(); + var referencesList = new List(); + foreach (BrowseResult result in results) + { + if (StatusCode.IsBad(result.StatusCode)) + { + errors.Add(new ServiceResult(result.StatusCode, ii, diagnosticInfos, response.ResponseHeader.StringTable)); + } + else + { + errors.Add(ServiceResult.Good); + } + revisedContinuationPoints.Add(result.ContinuationPoint); + referencesList.Add(result.References); + ii++; + } + + return (response.ResponseHeader, revisedContinuationPoints, referencesList, errors); + } + #endregion + #region Call Methods /// public async Task> CallAsync(NodeId objectId, NodeId methodId, CancellationToken ct = default, params object[] args) @@ -581,9 +1106,273 @@ public async Task> CallAsync(NodeId objectId, NodeId methodId, Can } #endregion + #region FetchReferences Async Methods + /// + public async Task FetchReferencesAsync( + NodeId nodeId, + CancellationToken ct = default) + { + // browse for all references. + + ReferenceDescriptionCollection results = new ReferenceDescriptionCollection(); + ( + _, + ByteStringCollection continuationPoint, + IList descriptions, + _ + ) = await BrowseAsync( + null, + null, + new[] { nodeId }, + 0, + BrowseDirection.Both, + null, + true, + 0, + ct).ConfigureAwait(false); + + if (descriptions.Count > 0) + { + results.AddRange(descriptions[0]); + + // process any continuation point. + while (continuationPoint != null && continuationPoint.Count > 0 & continuationPoint[0] != null) + { + ( + _, + ByteStringCollection revisedContinuationPoint, + IList additionalDescriptions, + _ + ) = await BrowseNextAsync( + null, + continuationPoint, + false, + ct).ConfigureAwait(false); + + continuationPoint = revisedContinuationPoint; + + if (additionalDescriptions.Count > 0) + results.AddRange(additionalDescriptions[0]); + } + } + return results; + } + + /// + public async Task<(IList, IList)> FetchReferencesAsync( + IList nodeIds, + CancellationToken ct = default) + { + var result = new List(); + + // browse for all references. + ( + _, + ByteStringCollection continuationPoints, + IList descriptions, + IList errors + ) = await BrowseAsync( + null, + null, + nodeIds, + 0, + BrowseDirection.Both, + null, + true, + 0, + ct).ConfigureAwait(false); + + result.AddRange(descriptions); + + // process any continuation point. + List previousResult = result; + IList previousErrors = errors; + while (HasAnyContinuationPoint(continuationPoints)) + { + var nextContinuationPoints = new ByteStringCollection(); + var nextResult = new List(); + var nextErrors = new List(); + + for (int ii = 0; ii < continuationPoints.Count; ii++) + { + byte[] cp = continuationPoints[ii]; + if (cp != null) + { + nextContinuationPoints.Add(cp); + nextResult.Add(previousResult[ii]); + nextErrors.Add(previousErrors[ii]); + } + } + + ( + _, + ByteStringCollection revisedContinuationPoints, + IList nextDescriptions, + IList browseNextErrors + ) = await BrowseNextAsync( + null, + nextContinuationPoints, + false, + ct).ConfigureAwait(false); + + continuationPoints = revisedContinuationPoints; + previousResult = nextResult; + previousErrors = nextErrors; + + for (int ii = 0; ii < nextDescriptions.Count; ii++) + { + nextResult[ii].AddRange(nextDescriptions[ii]); + if (StatusCode.IsBad(browseNextErrors[ii].StatusCode)) + { + nextErrors[ii] = browseNextErrors[ii]; + } + } + } + + return (result, errors); + } + #endregion + + #region Recreate Async Methods + /// + /// Recreates a session based on a specified template. + /// + /// The Session object to use as template + /// + /// The new session object. + public static async Task RecreateAsync(Session sessionTemplate, CancellationToken ct = default) + { + ServiceMessageContext messageContext = sessionTemplate.m_configuration.CreateMessageContext(); + messageContext.Factory = sessionTemplate.Factory; + + // create the channel object used to connect to the server. + ITransportChannel channel = SessionChannel.Create( + sessionTemplate.m_configuration, + sessionTemplate.ConfiguredEndpoint.Description, + sessionTemplate.ConfiguredEndpoint.Configuration, + sessionTemplate.m_instanceCertificate, + sessionTemplate.m_configuration.SecurityConfiguration.SendCertificateChain ? + sessionTemplate.m_instanceCertificateChain : null, + messageContext); + + // create the session object. + Session session = sessionTemplate.CloneSession(channel, true); + + try + { + // open the session. + await session.OpenAsync( + sessionTemplate.SessionName, + (uint)sessionTemplate.SessionTimeout, + sessionTemplate.Identity, + sessionTemplate.PreferredLocales, + sessionTemplate.m_checkDomain, + ct).ConfigureAwait(false); + + await session.RecreateSubscriptionsAsync(sessionTemplate.Subscriptions, ct).ConfigureAwait(false); + } + catch (Exception e) + { + session.Dispose(); + throw ServiceResultException.Create(StatusCodes.BadCommunicationError, e, "Could not recreate session. {0}", sessionTemplate.SessionName); + } + + return session; + } + + /// + /// Recreates a session based on a specified template. + /// + /// The Session object to use as template + /// The waiting reverse connection. + /// + /// The new session object. + public static async Task RecreateAsync(Session sessionTemplate, ITransportWaitingConnection connection, CancellationToken ct = default) + { + ServiceMessageContext messageContext = sessionTemplate.m_configuration.CreateMessageContext(); + messageContext.Factory = sessionTemplate.Factory; + + // create the channel object used to connect to the server. + ITransportChannel channel = SessionChannel.Create( + sessionTemplate.m_configuration, + connection, + sessionTemplate.m_endpoint.Description, + sessionTemplate.m_endpoint.Configuration, + sessionTemplate.m_instanceCertificate, + sessionTemplate.m_configuration.SecurityConfiguration.SendCertificateChain ? + sessionTemplate.m_instanceCertificateChain : null, + messageContext); + + // create the session object. + Session session = sessionTemplate.CloneSession(channel, true); + + try + { + // open the session. + await session.OpenAsync( + sessionTemplate.m_sessionName, + (uint)sessionTemplate.m_sessionTimeout, + sessionTemplate.m_identity, + sessionTemplate.m_preferredLocales, + sessionTemplate.m_checkDomain, + ct).ConfigureAwait(false); + + await session.RecreateSubscriptionsAsync(sessionTemplate.Subscriptions, ct).ConfigureAwait(false); + } + catch (Exception e) + { + session.Dispose(); + throw ServiceResultException.Create(StatusCodes.BadCommunicationError, e, "Could not recreate session. {0}", sessionTemplate.m_sessionName); + } + + return session; + } + + /// + /// Recreates a session based on a specified template using the provided channel. + /// + /// The Session object to use as template + /// The waiting reverse connection. + /// + /// The new session object. + public static async Task RecreateAsync(Session sessionTemplate, ITransportChannel transportChannel, CancellationToken ct = default) + { + ServiceMessageContext messageContext = sessionTemplate.m_configuration.CreateMessageContext(); + messageContext.Factory = sessionTemplate.Factory; + + // create the session object. + Session session = sessionTemplate.CloneSession(transportChannel, true); + + try + { + // open the session. + await session.OpenAsync( + sessionTemplate.m_sessionName, + (uint)sessionTemplate.m_sessionTimeout, + sessionTemplate.m_identity, + sessionTemplate.m_preferredLocales, + sessionTemplate.m_checkDomain, + ct).ConfigureAwait(false); + + // create the subscriptions. + foreach (Subscription subscription in session.Subscriptions) + { + await subscription.CreateAsync(ct).ConfigureAwait(false); + } + } + catch (Exception e) + { + session.Dispose(); + throw ServiceResultException.Create(StatusCodes.BadCommunicationError, e, "Could not recreate session. {0}", sessionTemplate.m_sessionName); + } + + return session; + } + #endregion + #region Close Async Methods /// - public Task CloseAsync(CancellationToken ct = default) + public override Task CloseAsync(CancellationToken ct = default) { return CloseAsync(m_keepAliveInterval, true, ct); } @@ -641,7 +1430,7 @@ public virtual async Task CloseAsync(int timeout, bool closeChannel, { // close the session and delete all subscriptions if specified. this.OperationTimeout = timeout; - var response = await base.CloseSessionAsync(null, m_deleteSubscriptionsOnClose, ct).ConfigureAwait(false); + CloseSessionResponse response = await base.CloseSessionAsync(null, m_deleteSubscriptionsOnClose, ct).ConfigureAwait(false); this.OperationTimeout = existingTimeout; if (closeChannel) @@ -682,6 +1471,194 @@ public virtual async Task CloseAsync(int timeout, bool closeChannel, return result; } #endregion + + #region Reconnect Async Methods + /// + public Task ReconnectAsync(CancellationToken ct) + => ReconnectAsync(null, null, ct); + + /// + public Task ReconnectAsync(ITransportWaitingConnection connection, CancellationToken ct) + => ReconnectAsync(connection, null, ct); + + /// + public Task ReconnectAsync(ITransportChannel channel, CancellationToken ct) + => ReconnectAsync(null, channel, ct); + + /// + /// Reconnects to the server after a network failure using a waiting connection. + /// + private async Task ReconnectAsync(ITransportWaitingConnection connection, ITransportChannel transportChannel, CancellationToken ct) + { + bool resetReconnect = false; + await m_reconnectLock.WaitAsync(ct).ConfigureAwait(false); + try + { + bool reconnecting = m_reconnecting; + m_reconnecting = true; + resetReconnect = true; + m_reconnectLock.Release(); + + // check if already connecting. + if (reconnecting) + { + Utils.LogWarning("Session is already attempting to reconnect."); + + throw ServiceResultException.Create( + StatusCodes.BadInvalidState, + "Session is already attempting to reconnect."); + } + + IAsyncResult result = PrepareReconnectBeginActivate( + connection, + transportChannel); + + if (!(result is ChannelAsyncOperation operation)) throw new ArgumentNullException(nameof(result)); + + try + { + _ = await operation.EndAsync(kReconnectTimeout / 2, true, ct).ConfigureAwait(false); + } + catch (ServiceResultException) + { + Utils.LogWarning("WARNING: ACTIVATE SESSION {0} timed out. {1}/{2}", SessionId, GoodPublishRequestCount, OutstandingRequestCount); + } + + // reactivate session. + byte[] serverNonce = null; + StatusCodeCollection certificateResults = null; + DiagnosticInfoCollection certificateDiagnosticInfos = null; + + EndActivateSession( + result, + out serverNonce, + out certificateResults, + out certificateDiagnosticInfos); + + int publishCount = 0; + + Utils.LogInfo("Session RECONNECT {0} completed successfully.", SessionId); + + lock (SyncRoot) + { + m_previousServerNonce = m_serverNonce; + m_serverNonce = serverNonce; + publishCount = GetMinPublishRequestCount(true); + } + + await m_reconnectLock.WaitAsync(ct).ConfigureAwait(false); + m_reconnecting = false; + resetReconnect = false; + m_reconnectLock.Release(); + + // refill pipeline. + for (int ii = 0; ii < publishCount; ii++) + { + BeginPublish(OperationTimeout); + } + + StartKeepAliveTimer(); + + IndicateSessionConfigurationChanged(); + } + finally + { + if (resetReconnect) + { + await m_reconnectLock.WaitAsync(ct).ConfigureAwait(false); + m_reconnecting = false; + m_reconnectLock.Release(); + } + } + } + + /// + public async Task RepublishAsync(uint subscriptionId, uint sequenceNumber, CancellationToken ct) + { + // send republish request. + RequestHeader requestHeader = new RequestHeader { + TimeoutHint = (uint)OperationTimeout, + ReturnDiagnostics = (uint)(int)ReturnDiagnostics, + RequestHandle = Utils.IncrementIdentifier(ref m_publishCounter) + }; + + try + { + Utils.LogInfo("Requesting RepublishAsync for {0}-{1}", subscriptionId, sequenceNumber); + + // request republish. + RepublishResponse response = await RepublishAsync( + requestHeader, + subscriptionId, + sequenceNumber, + ct).ConfigureAwait(false); + ResponseHeader responseHeader = response.ResponseHeader; + NotificationMessage notificationMessage = response.NotificationMessage; + + Utils.LogInfo("Received RepublishAsync for {0}-{1}-{2}", subscriptionId, sequenceNumber, responseHeader.ServiceResult); + + // process response. + ProcessPublishResponse( + responseHeader, + subscriptionId, + null, + false, + notificationMessage); + + return true; + } + catch (Exception e) + { + return ProcessRepublishResponseError(e, subscriptionId, sequenceNumber); + } + } + + /// + /// Recreate the subscriptions in a reconnected session. + /// Uses Transfer service if is set to true. + /// + /// The template for the subscriptions. + /// + private async Task RecreateSubscriptionsAsync(IEnumerable subscriptionsTemplate, CancellationToken ct) + { + bool transferred = false; + if (TransferSubscriptionsOnReconnect) + { + try + { + transferred = await TransferSubscriptionsAsync(new SubscriptionCollection(subscriptionsTemplate), false, ct).ConfigureAwait(false); + } + catch (ServiceResultException sre) + { + if (sre.StatusCode == StatusCodes.BadServiceUnsupported) + { + TransferSubscriptionsOnReconnect = false; + Utils.LogWarning("Transfer subscription unsupported, TransferSubscriptionsOnReconnect set to false."); + } + else + { + Utils.LogError(sre, "Transfer subscriptions failed."); + } + } + catch (Exception ex) + { + Utils.LogError(ex, "Unexpected Transfer subscriptions error."); + } + } + + if (!transferred) + { + // Create the subscriptions which were not transferred. + foreach (Subscription subscription in Subscriptions) + { + if (!subscription.Created) + { + await subscription.CreateAsync(ct).ConfigureAwait(false); + } + } + } + } + #endregion } } #endif diff --git a/Libraries/Opc.Ua.Client/SessionConfiguration.cs b/Libraries/Opc.Ua.Client/SessionConfiguration.cs index 3d67b80a7..b337e06a0 100644 --- a/Libraries/Opc.Ua.Client/SessionConfiguration.cs +++ b/Libraries/Opc.Ua.Client/SessionConfiguration.cs @@ -27,6 +27,7 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ +using System; using System.IO; using System.Runtime.Serialization; using System.Xml; @@ -38,6 +39,11 @@ namespace Opc.Ua.Client /// needed to reconnect a session with a new secure channel. /// [DataContract(Namespace = Namespaces.OpcUaXsd)] + [KnownType(typeof(UserIdentityToken))] + [KnownType(typeof(AnonymousIdentityToken))] + [KnownType(typeof(X509IdentityToken))] + [KnownType(typeof(IssuedIdentityToken))] + [KnownType(typeof(UserIdentity))] public class SessionConfiguration { /// @@ -45,6 +51,7 @@ public class SessionConfiguration /// internal SessionConfiguration(ISession session, byte[] serverNonce, NodeId authenthicationToken) { + Timestamp = DateTime.UtcNow; SessionName = session.SessionName; SessionId = session.SessionId; AuthenticationToken = authenthicationToken; @@ -63,54 +70,58 @@ public static SessionConfiguration Create(Stream stream) XmlReaderSettings settings = Utils.DefaultXmlReaderSettings(); using (XmlReader reader = XmlReader.Create(stream, settings)) { - DataContractSerializer serializer = new DataContractSerializer(typeof(SessionConfiguration), - new [] { typeof(UserIdentityToken), typeof(AnonymousIdentityToken), typeof(X509IdentityToken), - typeof(IssuedIdentityToken), typeof(UserIdentity) }); + DataContractSerializer serializer = new DataContractSerializer(typeof(SessionConfiguration)); SessionConfiguration sessionConfiguration = (SessionConfiguration)serializer.ReadObject(reader); return sessionConfiguration; } } + /// + /// When the session configuration was created. + /// + [DataMember(IsRequired = true, Order = 10)] + public DateTime Timestamp { get; set; } + /// /// The session name used by the client. /// - [DataMember(IsRequired=true, Order = 10)] + [DataMember(IsRequired = true, Order = 20)] public string SessionName { get; set; } /// /// The session id assigned by the server. /// - [DataMember(IsRequired = true, Order = 20)] + [DataMember(IsRequired = true, Order = 30)] public NodeId SessionId { get; set; } /// /// The authentication token used by the server to identify the session. /// - [DataMember(IsRequired = true, Order = 30)] + [DataMember(IsRequired = true, Order = 40)] public NodeId AuthenticationToken { get; set; } /// /// The identity used to create the session. /// - [DataMember(IsRequired = true, Order = 40)] + [DataMember(IsRequired = true, Order = 50)] public IUserIdentity Identity { get; set; } /// /// The configured endpoint for the secure channel. /// - [DataMember(IsRequired = true, Order = 50)] + [DataMember(IsRequired = true, Order = 60)] public ConfiguredEndpoint ConfiguredEndpoint { get; set; } /// /// If the client is configured to check the certificate domain. /// - [DataMember(IsRequired = false, Order = 60)] + [DataMember(IsRequired = false, Order = 70)] public bool CheckDomain { get; set; } /// /// The last server nonce received. /// - [DataMember(IsRequired = true, Order = 70)] + [DataMember(IsRequired = true, Order = 80)] public byte[] ServerNonce { get; set; } } } diff --git a/Libraries/Opc.Ua.Client/SessionObsolete.cs b/Libraries/Opc.Ua.Client/SessionObsolete.cs index 12c75a9f5..b4c35d727 100644 --- a/Libraries/Opc.Ua.Client/SessionObsolete.cs +++ b/Libraries/Opc.Ua.Client/SessionObsolete.cs @@ -40,7 +40,7 @@ namespace Opc.Ua.Client /// /// Obsolete warnings for service calls which should not be used when using the Session API. /// - public partial class Session : SessionClientBatched, ISession, IDisposable + public partial class Session : SessionClientBatched, ISession { /// [Obsolete("Call Create instead. Service Call doesn't create Session.")] diff --git a/Libraries/Opc.Ua.Client/SessionReconnectHandler.cs b/Libraries/Opc.Ua.Client/SessionReconnectHandler.cs index d4485934e..e66204940 100644 --- a/Libraries/Opc.Ua.Client/SessionReconnectHandler.cs +++ b/Libraries/Opc.Ua.Client/SessionReconnectHandler.cs @@ -95,7 +95,7 @@ public enum ReconnectState public SessionReconnectHandler(bool reconnectAbort = false, int maxReconnectPeriod = -1) { m_reconnectAbort = reconnectAbort; - m_reconnectTimer = new Timer(OnReconnect, this, Timeout.Infinite, Timeout.Infinite); + m_reconnectTimer = new Timer(OnReconnectAsync, this, Timeout.Infinite, Timeout.Infinite); m_state = ReconnectState.Ready; m_cancelReconnect = false; m_updateFromServer = false; @@ -285,7 +285,7 @@ public virtual int CheckedReconnectPeriod(int reconnectPeriod, bool exponentialB /// /// Called when the reconnect timer expires. /// - private async void OnReconnect(object state) + private async void OnReconnectAsync(object state) { DateTime reconnectStart = DateTime.UtcNow; try @@ -320,7 +320,7 @@ private async void OnReconnect(object state) // do the reconnect. if (keepaliveRecovered || - await DoReconnect().ConfigureAwait(false)) + await DoReconnectAsync().ConfigureAwait(false)) { lock (m_lock) { @@ -365,7 +365,7 @@ await DoReconnect().ConfigureAwait(false)) /// /// Reconnects to the server. /// - private async Task DoReconnect() + private async Task DoReconnectAsync() { // helper to override operation timeout int operationTimeout = m_session.OperationTimeout; @@ -384,11 +384,11 @@ private async Task DoReconnect() m_session.Endpoint.Server.ApplicationUri ).ConfigureAwait(false); - m_session.Reconnect(connection); + await m_session.ReconnectAsync(connection).ConfigureAwait(false); } else { - m_session.Reconnect(); + await m_session.ReconnectAsync().ConfigureAwait(false); } // monitored items should start updating on their own. @@ -418,12 +418,15 @@ private async Task DoReconnect() } // check if the security configuration may have changed - if (sre.StatusCode == StatusCodes.BadSecurityChecksFailed) + if (sre.StatusCode == StatusCodes.BadSecurityChecksFailed || + sre.StatusCode == StatusCodes.BadCertificateInvalid) { m_updateFromServer = true; Utils.LogInfo("Reconnect failed due to security check. Request endpoint update from server. {0}", sre.Message); } - else + // wait for next scheduled reconnect if connection failed, + // otherwise recreate session immediately + else if (sre.StatusCode != StatusCodes.BadSessionIdInvalid) { // next attempt is to recreate session m_reconnectFailed = true; @@ -486,13 +489,15 @@ await endpoint.UpdateFromServerAsync( session = await m_session.SessionFactory.RecreateAsync(m_session).ConfigureAwait(false); } - m_session.Close(); + // note: the template session is not connected at this point + // and must be disposed by the owner m_session = session; return true; } catch (ServiceResultException sre) { - if (sre.InnerResult?.StatusCode == StatusCodes.BadSecurityChecksFailed) + if (sre.InnerResult?.StatusCode == StatusCodes.BadSecurityChecksFailed || + sre.InnerResult?.StatusCode == StatusCodes.BadCertificateInvalid) { // schedule endpoint update and retry m_updateFromServer = true; @@ -533,7 +538,7 @@ private void EnterReadyState() #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private ISession m_session; private ReconnectState m_state; private Random m_random; diff --git a/Libraries/Opc.Ua.Client/Subscription.cs b/Libraries/Opc.Ua.Client/Subscription.cs index 2787245bf..a8a3dde8c 100644 --- a/Libraries/Opc.Ua.Client/Subscription.cs +++ b/Libraries/Opc.Ua.Client/Subscription.cs @@ -28,7 +28,6 @@ * ======================================================================*/ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; @@ -122,7 +121,7 @@ public Subscription(Subscription template, bool copyEventHandlers) // copy the list of monitored items. foreach (MonitoredItem monitoredItem in template.MonitoredItems) { - MonitoredItem clone = new MonitoredItem(monitoredItem, copyEventHandlers, true); + MonitoredItem clone = monitoredItem.CloneMonitoredItem(copyEventHandlers, true); clone.DisplayName = monitoredItem.DisplayName; AddItem(clone); } @@ -146,7 +145,7 @@ private void ResetPublishTimerAndWorkerState() /// Called by the .NET framework during deserialization. /// [OnDeserializing] - private void Initialize(StreamingContext context) + protected void Initialize(StreamingContext context) { m_cache = new object(); Initialize(); @@ -219,8 +218,16 @@ public virtual object Clone() /// public new object MemberwiseClone() { - var clone = new Subscription(this); - return clone; + return new Subscription(this); + } + + /// + /// Clones a subscription or a subclass with an option to copy event handlers. + /// + /// A cloned instance of the subscription or its subclass. + public virtual Subscription CloneSubscription(bool copyEventHandlers) + { + return new Subscription(this, copyEventHandlers); } #endregion @@ -797,9 +804,10 @@ public bool PublishingStopped { lock (m_cache) { - int keepAliveInterval = (int)(Math.Min(m_currentPublishingInterval * m_currentKeepAliveCount, Int32.MaxValue - 500)); + int keepAliveInterval = (int)(Math.Min(m_currentPublishingInterval * (m_currentKeepAliveCount + 1), Int32.MaxValue - 500)); + TimeSpan timeSinceLastNotification = DateTime.UtcNow - m_lastNotificationTime; - if (m_lastNotificationTime.AddMilliseconds(keepAliveInterval + 500) < DateTime.UtcNow) + if (timeSinceLastNotification.TotalMilliseconds > keepAliveInterval + 500) { return true; } @@ -1822,7 +1830,7 @@ private void StartKeepAliveTimer() if (m_messageWorkerTask == null || m_messageWorkerTask.IsCompleted) { m_messageWorkerShutdownEvent.Reset(); - m_messageWorkerTask = Task.Run(() => PublishResponseMessageWorker()); + m_messageWorkerTask = Task.Run(() => PublishResponseMessageWorkerAsync()); } } @@ -1869,9 +1877,9 @@ private void OnKeepAlive(object state) } /// - /// Periodically checks if the sessions have timed out. + /// Publish response worker task for the subscriptions. /// - private async Task PublishResponseMessageWorker() + private async Task PublishResponseMessageWorkerAsync() { try { @@ -1886,7 +1894,8 @@ private async Task PublishResponseMessageWorker() Utils.LogTrace("SubscriptionId {0} - Publish Thread {1:X8} Exited Normally.", m_id, Environment.CurrentManagedThreadId); break; } - OnMessageReceived(); + + await OnMessageReceivedAsync(CancellationToken.None).ConfigureAwait(false); } while (true); } @@ -2084,7 +2093,7 @@ private void AdjustCounts(ref uint keepAliveCount, ref uint lifetimeCount) /// /// Processes the incoming messages. /// - private void OnMessageReceived() + private async Task OnMessageReceivedAsync(CancellationToken ct) { try { @@ -2256,6 +2265,9 @@ private void OnMessageReceived() if (statusChanged != null) { + statusChanged.PublishTime = message.PublishTime; + statusChanged.SequenceNumber = message.SequenceNumber; + Utils.LogWarning("StatusChangeNotification received with Status = {0} for SubscriptionId={1}.", statusChanged.Status.ToString(), Id); @@ -2300,7 +2312,7 @@ private void OnMessageReceived() { for (int ii = 0; ii < messagesToRepublish.Count; ii++) { - if (!session.Republish(subscriptionId, messagesToRepublish[ii].SequenceNumber)) + if (!await session.RepublishAsync(subscriptionId, messagesToRepublish[ii].SequenceNumber, ct).ConfigureAwait(false)) { messagesToRepublish[ii].Republished = false; } @@ -2951,6 +2963,17 @@ public virtual object Clone() clone.AddRange(this.Select(item => (Subscription)item.Clone())); return clone; } + + /// + /// Helper to clone a SubscriptionCollection with event handlers using the + /// method. + /// + public virtual SubscriptionCollection CloneSubscriptions(bool copyEventhandlers) + { + SubscriptionCollection clone = new SubscriptionCollection(); + clone.AddRange(this.Select(item => (Subscription)item.CloneSubscription(copyEventhandlers))); + return clone; + } #endregion } } diff --git a/Libraries/Opc.Ua.Client/SubscriptionAsync.cs b/Libraries/Opc.Ua.Client/SubscriptionAsync.cs index 4f40ca547..dfae46627 100644 --- a/Libraries/Opc.Ua.Client/SubscriptionAsync.cs +++ b/Libraries/Opc.Ua.Client/SubscriptionAsync.cs @@ -452,7 +452,7 @@ public async Task> SetMonitoringModeAsync( /// /// Tells the server to refresh all conditions being monitored by the subscription. /// - public async Task ConditionRefreshAsync(CancellationToken ct = default(CancellationToken)) + public async Task ConditionRefreshAsync(CancellationToken ct = default) { VerifySubscriptionState(true); diff --git a/Libraries/Opc.Ua.Client/TraceableSession.cs b/Libraries/Opc.Ua.Client/TraceableSession.cs index 5b9c5a782..9c42f568c 100644 --- a/Libraries/Opc.Ua.Client/TraceableSession.cs +++ b/Libraries/Opc.Ua.Client/TraceableSession.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2023 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 @@ -11,7 +11,7 @@ * 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, @@ -31,6 +31,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -65,7 +68,7 @@ public TraceableSession(ISession session) /// /// The ISession which is being traced. /// - private ISession m_session; + private readonly ISession m_session; /// public ISession Session => m_session; @@ -272,10 +275,26 @@ public int OperationTimeout /// public bool CheckDomain => m_session.CheckDomain; + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) return true; + // Presume that the wrapper is being compared to the + // wrapped object, e.g. in a keep alive callback. + if (ReferenceEquals(m_session, obj)) return true; + return m_session?.Equals(obj) ?? false; + } + + /// + public override int GetHashCode() + { + return m_session?.GetHashCode() ?? base.GetHashCode(); + } + /// public void Reconnect() { - using (Activity activity = ActivitySource.StartActivity(nameof(Reconnect))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.Reconnect(); } @@ -284,7 +303,7 @@ public void Reconnect() /// public void Reconnect(ITransportWaitingConnection connection) { - using (Activity activity = ActivitySource.StartActivity(nameof(Reconnect))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.Reconnect(connection); } @@ -293,61 +312,88 @@ public void Reconnect(ITransportWaitingConnection connection) /// public void Reconnect(ITransportChannel channel) { - using (Activity activity = ActivitySource.StartActivity(nameof(Reconnect))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.Reconnect(channel); } } /// - public void Save(string filePath) + public async Task ReconnectAsync(CancellationToken ct = default) + { + using (Activity activity = ActivitySource.StartActivity()) + { + await m_session.ReconnectAsync(ct).ConfigureAwait(false); + } + } + + /// + public async Task ReconnectAsync(ITransportWaitingConnection connection, CancellationToken ct = default) + { + using (Activity activity = ActivitySource.StartActivity()) + { + await m_session.ReconnectAsync(connection, ct).ConfigureAwait(false); + } + } + + /// + public async Task ReconnectAsync(ITransportChannel channel, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(Save))) + using (Activity activity = ActivitySource.StartActivity()) { - m_session.Save(filePath); + await m_session.ReconnectAsync(channel, ct).ConfigureAwait(false); } } /// - public void Save(Stream stream, IEnumerable subscriptions) + public void Save(string filePath, IEnumerable knownTypes = null) { - using (Activity activity = ActivitySource.StartActivity(nameof(Save))) + using (Activity activity = ActivitySource.StartActivity()) { - m_session.Save(stream, subscriptions); + m_session.Save(filePath, knownTypes); } } /// - public void Save(string filePath, IEnumerable subscriptions) + public void Save(Stream stream, IEnumerable subscriptions, IEnumerable knownTypes = null) { - using (Activity activity = ActivitySource.StartActivity(nameof(Save))) + using (Activity activity = ActivitySource.StartActivity()) { - m_session.Save(filePath, subscriptions); + m_session.Save(stream, subscriptions, knownTypes); } } /// - public IEnumerable Load(Stream stream, bool transferSubscriptions = false) + public void Save(string filePath, IEnumerable subscriptions, IEnumerable knownTypes = null) { - using (Activity activity = ActivitySource.StartActivity(nameof(Load))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.Load(stream, transferSubscriptions); + m_session.Save(filePath, subscriptions, knownTypes); } } /// - public IEnumerable Load(string filePath, bool transferSubscriptions = false) + public IEnumerable Load(Stream stream, bool transferSubscriptions = false, IEnumerable knownTypes = null) { - using (Activity activity = ActivitySource.StartActivity(nameof(Load))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.Load(filePath, transferSubscriptions); + return m_session.Load(stream, transferSubscriptions, knownTypes); + } + } + + /// + public IEnumerable Load(string filePath, bool transferSubscriptions = false, IEnumerable knownTypes = null) + { + using (Activity activity = ActivitySource.StartActivity()) + { + return m_session.Load(filePath, transferSubscriptions, knownTypes); } } /// public void FetchNamespaceTables() { - using (Activity activity = ActivitySource.StartActivity(nameof(FetchNamespaceTables))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.FetchNamespaceTables(); } @@ -356,7 +402,7 @@ public void FetchNamespaceTables() /// public void FetchTypeTree(ExpandedNodeId typeId) { - using (Activity activity = ActivitySource.StartActivity(nameof(FetchTypeTree))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.FetchTypeTree(typeId); } @@ -365,16 +411,34 @@ public void FetchTypeTree(ExpandedNodeId typeId) /// public void FetchTypeTree(ExpandedNodeIdCollection typeIds) { - using (Activity activity = ActivitySource.StartActivity(nameof(FetchTypeTree))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.FetchTypeTree(typeIds); } } + /// + public async Task FetchTypeTreeAsync(ExpandedNodeId typeId, CancellationToken ct = default) + { + using (Activity activity = ActivitySource.StartActivity()) + { + await m_session.FetchTypeTreeAsync(typeId, ct).ConfigureAwait(false); + } + } + + /// + public async Task FetchTypeTreeAsync(ExpandedNodeIdCollection typeIds, CancellationToken ct = default) + { + using (Activity activity = ActivitySource.StartActivity()) + { + await m_session.FetchTypeTreeAsync(typeIds, ct).ConfigureAwait(false); + } + } + /// public ReferenceDescriptionCollection ReadAvailableEncodings(NodeId variableId) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadAvailableEncodings))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.ReadAvailableEncodings(variableId); } @@ -383,43 +447,43 @@ public ReferenceDescriptionCollection ReadAvailableEncodings(NodeId variableId) /// public ReferenceDescription FindDataDescription(NodeId encodingId) { - using (Activity activity = ActivitySource.StartActivity(nameof(FindDataDescription))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.FindDataDescription(encodingId); } } /// - public Task FindDataDictionary(NodeId descriptionId) + public async Task FindDataDictionary(NodeId descriptionId, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(FindDataDictionary))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.FindDataDictionary(descriptionId); + return await m_session.FindDataDictionary(descriptionId, ct).ConfigureAwait(false); } } /// - public async Task LoadDataDictionary(ReferenceDescription dictionaryNode, bool forceReload = false) + public DataDictionary LoadDataDictionary(ReferenceDescription dictionaryNode, bool forceReload = false) { - using (Activity activity = ActivitySource.StartActivity(nameof(LoadDataDictionary))) + using (Activity activity = ActivitySource.StartActivity()) { - return await m_session.LoadDataDictionary(dictionaryNode, forceReload).ConfigureAwait(false); + return m_session.LoadDataDictionary(dictionaryNode, forceReload); } } /// - public async Task> LoadDataTypeSystem(NodeId dataTypeSystem = null) + public async Task> LoadDataTypeSystem(NodeId dataTypeSystem = null, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(LoadDataTypeSystem))) + using (Activity activity = ActivitySource.StartActivity()) { - return await m_session.LoadDataTypeSystem(dataTypeSystem).ConfigureAwait(false); + return await m_session.LoadDataTypeSystem(dataTypeSystem, ct).ConfigureAwait(false); } } /// public Node ReadNode(NodeId nodeId) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadNode))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.ReadNode(nodeId); } @@ -428,7 +492,7 @@ public Node ReadNode(NodeId nodeId) /// public Node ReadNode(NodeId nodeId, NodeClass nodeClass, bool optionalAttributes = true) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadNode))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.ReadNode(nodeId, nodeClass, optionalAttributes); } @@ -437,7 +501,7 @@ public Node ReadNode(NodeId nodeId, NodeClass nodeClass, bool optionalAttributes /// public void ReadNodes(IList nodeIds, out IList nodeCollection, out IList errors, bool optionalAttributes = false) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadNodes))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.ReadNodes(nodeIds, out nodeCollection, out errors, optionalAttributes); } @@ -446,7 +510,7 @@ public void ReadNodes(IList nodeIds, out IList nodeCollection, out /// public void ReadNodes(IList nodeIds, NodeClass nodeClass, out IList nodeCollection, out IList errors, bool optionalAttributes = false) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadNodes))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.ReadNodes(nodeIds, nodeClass, out nodeCollection, out errors, optionalAttributes); } @@ -455,7 +519,7 @@ public void ReadNodes(IList nodeIds, NodeClass nodeClass, out IList public DataValue ReadValue(NodeId nodeId) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadValue))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.ReadValue(nodeId); } @@ -464,7 +528,7 @@ public DataValue ReadValue(NodeId nodeId) /// public object ReadValue(NodeId nodeId, Type expectedType) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadValue))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.ReadValue(nodeId, expectedType); } @@ -473,7 +537,7 @@ public object ReadValue(NodeId nodeId, Type expectedType) /// public void ReadValues(IList nodeIds, out DataValueCollection values, out IList errors) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadValues))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.ReadValues(nodeIds, out values, out errors); } @@ -482,7 +546,7 @@ public void ReadValues(IList nodeIds, out DataValueCollection values, ou /// public ReferenceDescriptionCollection FetchReferences(NodeId nodeId) { - using (Activity activity = ActivitySource.StartActivity(nameof(FetchReferences))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.FetchReferences(nodeId); } @@ -491,16 +555,34 @@ public ReferenceDescriptionCollection FetchReferences(NodeId nodeId) /// public void FetchReferences(IList nodeIds, out IList referenceDescriptions, out IList errors) { - using (Activity activity = ActivitySource.StartActivity(nameof(FetchReferences))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.FetchReferences(nodeIds, out referenceDescriptions, out errors); } } + /// + public async Task FetchReferencesAsync(NodeId nodeId, CancellationToken ct) + { + using (Activity activity = ActivitySource.StartActivity()) + { + return await m_session.FetchReferencesAsync(nodeId, ct).ConfigureAwait(false); + } + } + + /// + public async Task<(IList, IList)> FetchReferencesAsync(IList nodeIds, CancellationToken ct) + { + using (Activity activity = ActivitySource.StartActivity()) + { + return await m_session.FetchReferencesAsync(nodeIds, ct).ConfigureAwait(false); + } + } + /// public void Open(string sessionName, IUserIdentity identity) { - using (Activity activity = ActivitySource.StartActivity(nameof(Open))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.Open(sessionName, identity); } @@ -509,7 +591,7 @@ public void Open(string sessionName, IUserIdentity identity) /// public void Open(string sessionName, uint sessionTimeout, IUserIdentity identity, IList preferredLocales) { - using (Activity activity = ActivitySource.StartActivity(nameof(Open))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.Open(sessionName, sessionTimeout, identity, preferredLocales); } @@ -518,7 +600,7 @@ public void Open(string sessionName, uint sessionTimeout, IUserIdentity identity /// public void Open(string sessionName, uint sessionTimeout, IUserIdentity identity, IList preferredLocales, bool checkDomain) { - using (Activity activity = ActivitySource.StartActivity(nameof(Open))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.Open(sessionName, sessionTimeout, identity, preferredLocales, checkDomain); } @@ -527,7 +609,7 @@ public void Open(string sessionName, uint sessionTimeout, IUserIdentity identity /// public void ChangePreferredLocales(StringCollection preferredLocales) { - using (Activity activity = ActivitySource.StartActivity(nameof(ChangePreferredLocales))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.ChangePreferredLocales(preferredLocales); } @@ -536,7 +618,7 @@ public void ChangePreferredLocales(StringCollection preferredLocales) /// public void UpdateSession(IUserIdentity identity, StringCollection preferredLocales) { - using (Activity activity = ActivitySource.StartActivity(nameof(UpdateSession))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.UpdateSession(identity, preferredLocales); } @@ -545,7 +627,7 @@ public void UpdateSession(IUserIdentity identity, StringCollection preferredLoca /// public void FindComponentIds(NodeId instanceId, IList componentPaths, out NodeIdCollection componentIds, out List errors) { - using (Activity activity = ActivitySource.StartActivity(nameof(FindComponentIds))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.FindComponentIds(instanceId, componentPaths, out componentIds, out errors); } @@ -554,7 +636,7 @@ public void FindComponentIds(NodeId instanceId, IList componentPaths, ou /// public void ReadValues(IList variableIds, IList expectedTypes, out List values, out List errors) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadValues))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.ReadValues(variableIds, expectedTypes, out values, out errors); } @@ -563,15 +645,53 @@ public void ReadValues(IList variableIds, IList expectedTypes, out /// public void ReadDisplayName(IList nodeIds, out IList displayNames, out IList errors) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadDisplayName))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.ReadDisplayName(nodeIds, out displayNames, out errors); } } + + /// + public async Task OpenAsync(string sessionName, IUserIdentity identity, CancellationToken ct) + { + using (Activity activity = ActivitySource.StartActivity()) + { + await m_session.OpenAsync(sessionName, identity, ct).ConfigureAwait(false); + } + } + + /// + public async Task OpenAsync(string sessionName, uint sessionTimeout, IUserIdentity identity, IList preferredLocales, CancellationToken ct) + { + using (Activity activity = ActivitySource.StartActivity()) + { + await m_session.OpenAsync(sessionName, sessionTimeout, identity, preferredLocales, ct).ConfigureAwait(false); + } + } + + /// + public async Task OpenAsync(string sessionName, uint sessionTimeout, IUserIdentity identity, IList preferredLocales, bool checkDomain, CancellationToken ct) + { + using (Activity activity = ActivitySource.StartActivity()) + { + await m_session.OpenAsync(sessionName, sessionTimeout, identity, preferredLocales, checkDomain, ct).ConfigureAwait(false); + } + } + + + /// + public async Task FetchNamespaceTablesAsync(CancellationToken ct = default) + { + using (Activity activity = ActivitySource.StartActivity()) + { + await m_session.FetchNamespaceTablesAsync(ct).ConfigureAwait(false); + } + } + /// public async Task<(IList, IList)> ReadNodesAsync(IList nodeIds, NodeClass nodeClass, bool optionalAttributes = false, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadNodesAsync))) + using (Activity activity = ActivitySource.StartActivity()) { return await m_session.ReadNodesAsync(nodeIds, nodeClass, optionalAttributes, ct).ConfigureAwait(false); } @@ -580,7 +700,7 @@ public void ReadDisplayName(IList nodeIds, out IList displayName /// public async Task ReadValueAsync(NodeId nodeId, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadValueAsync))) + using (Activity activity = ActivitySource.StartActivity()) { return await m_session.ReadValueAsync(nodeId, ct).ConfigureAwait(false); } @@ -589,7 +709,7 @@ public async Task ReadValueAsync(NodeId nodeId, CancellationToken ct /// public async Task ReadNodeAsync(NodeId nodeId, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadNodeAsync))) + using (Activity activity = ActivitySource.StartActivity()) { return await m_session.ReadNodeAsync(nodeId, ct).ConfigureAwait(false); } @@ -598,7 +718,7 @@ public async Task ReadNodeAsync(NodeId nodeId, CancellationToken ct = defa /// public async Task ReadNodeAsync(NodeId nodeId, NodeClass nodeClass, bool optionalAttributes = true, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadNodeAsync))) + using (Activity activity = ActivitySource.StartActivity()) { return await m_session.ReadNodeAsync(nodeId, nodeClass, optionalAttributes, ct).ConfigureAwait(false); } @@ -607,7 +727,7 @@ public async Task ReadNodeAsync(NodeId nodeId, NodeClass nodeClass, bool o /// public async Task<(IList, IList)> ReadNodesAsync(IList nodeIds, bool optionalAttributes = false, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadNodesAsync))) + using (Activity activity = ActivitySource.StartActivity()) { return await m_session.ReadNodesAsync(nodeIds, optionalAttributes, ct).ConfigureAwait(false); } @@ -616,7 +736,7 @@ public async Task ReadNodeAsync(NodeId nodeId, NodeClass nodeClass, bool o /// public async Task<(DataValueCollection, IList)> ReadValuesAsync(IList nodeIds, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadValuesAsync))) + using (Activity activity = ActivitySource.StartActivity()) { return await m_session.ReadValuesAsync(nodeIds, ct).ConfigureAwait(false); } @@ -625,7 +745,7 @@ public async Task ReadNodeAsync(NodeId nodeId, NodeClass nodeClass, bool o /// public StatusCode Close(int timeout) { - using (Activity activity = ActivitySource.StartActivity(nameof(Close))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.Close(timeout); } @@ -634,7 +754,7 @@ public StatusCode Close(int timeout) /// public StatusCode Close(bool closeChannel) { - using (Activity activity = ActivitySource.StartActivity(nameof(Close))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.Close(closeChannel); } @@ -643,7 +763,7 @@ public StatusCode Close(bool closeChannel) /// public StatusCode Close(int timeout, bool closeChannel) { - using (Activity activity = ActivitySource.StartActivity(nameof(Close))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.Close(timeout, closeChannel); } @@ -652,7 +772,7 @@ public StatusCode Close(int timeout, bool closeChannel) /// public async Task CloseAsync(CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(CloseAsync))) + using (Activity activity = ActivitySource.StartActivity()) { return await m_session.CloseAsync(ct).ConfigureAwait(false); } @@ -661,7 +781,7 @@ public async Task CloseAsync(CancellationToken ct = default) /// public async Task CloseAsync(bool closeChannel, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(CloseAsync))) + using (Activity activity = ActivitySource.StartActivity()) { return await m_session.CloseAsync(closeChannel, ct).ConfigureAwait(false); } @@ -670,7 +790,7 @@ public async Task CloseAsync(bool closeChannel, CancellationToken ct /// public async Task CloseAsync(int timeout, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(CloseAsync))) + using (Activity activity = ActivitySource.StartActivity()) { return await m_session.CloseAsync(timeout, ct).ConfigureAwait(false); } @@ -679,7 +799,7 @@ public async Task CloseAsync(int timeout, CancellationToken ct = def /// public async Task CloseAsync(int timeout, bool closeChannel, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(CloseAsync))) + using (Activity activity = ActivitySource.StartActivity()) { return await m_session.CloseAsync(timeout, closeChannel, ct).ConfigureAwait(false); } @@ -688,7 +808,7 @@ public async Task CloseAsync(int timeout, bool closeChannel, Cancell /// public bool AddSubscription(Subscription subscription) { - using (Activity activity = ActivitySource.StartActivity(nameof(AddSubscription))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.AddSubscription(subscription); } @@ -697,7 +817,7 @@ public bool AddSubscription(Subscription subscription) /// public bool RemoveSubscription(Subscription subscription) { - using (Activity activity = ActivitySource.StartActivity(nameof(RemoveSubscription))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.RemoveSubscription(subscription); } @@ -706,7 +826,7 @@ public bool RemoveSubscription(Subscription subscription) /// public bool RemoveSubscriptions(IEnumerable subscriptions) { - using (Activity activity = ActivitySource.StartActivity(nameof(RemoveSubscriptions))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.RemoveSubscriptions(subscriptions); } @@ -715,7 +835,7 @@ public bool RemoveSubscriptions(IEnumerable subscriptions) /// public bool TransferSubscriptions(SubscriptionCollection subscriptions, bool sendInitialValues) { - using (Activity activity = ActivitySource.StartActivity(nameof(TransferSubscriptions))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.TransferSubscriptions(subscriptions, sendInitialValues); } @@ -724,7 +844,7 @@ public bool TransferSubscriptions(SubscriptionCollection subscriptions, bool sen /// public bool RemoveTransferredSubscription(Subscription subscription) { - using (Activity activity = ActivitySource.StartActivity(nameof(RemoveTransferredSubscription))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.RemoveTransferredSubscription(subscription); } @@ -733,7 +853,7 @@ public bool RemoveTransferredSubscription(Subscription subscription) /// public async Task RemoveSubscriptionAsync(Subscription subscription) { - using (Activity activity = ActivitySource.StartActivity(nameof(RemoveSubscriptionAsync))) + using (Activity activity = ActivitySource.StartActivity()) { return await m_session.RemoveSubscriptionAsync(subscription).ConfigureAwait(false); } @@ -742,7 +862,7 @@ public async Task RemoveSubscriptionAsync(Subscription subscription) /// public async Task RemoveSubscriptionsAsync(IEnumerable subscriptions) { - using (Activity activity = ActivitySource.StartActivity(nameof(RemoveSubscriptionsAsync))) + using (Activity activity = ActivitySource.StartActivity()) { return await m_session.RemoveSubscriptionsAsync(subscriptions).ConfigureAwait(false); } @@ -751,7 +871,7 @@ public async Task RemoveSubscriptionsAsync(IEnumerable subsc /// public ResponseHeader Browse(RequestHeader requestHeader, ViewDescription view, NodeId nodeToBrowse, uint maxResultsToReturn, BrowseDirection browseDirection, NodeId referenceTypeId, bool includeSubtypes, uint nodeClassMask, out byte[] continuationPoint, out ReferenceDescriptionCollection references) { - using (Activity activity = ActivitySource.StartActivity(nameof(Browse))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.Browse(requestHeader, view, nodeToBrowse, maxResultsToReturn, browseDirection, referenceTypeId, includeSubtypes, nodeClassMask, out continuationPoint, out references); } @@ -772,7 +892,7 @@ public ResponseHeader EndBrowse(IAsyncResult result, out byte[] continuationPoin /// public ResponseHeader BrowseNext(RequestHeader requestHeader, bool releaseContinuationPoint, byte[] continuationPoint, out byte[] revisedContinuationPoint, out ReferenceDescriptionCollection references) { - using (Activity activity = ActivitySource.StartActivity(nameof(BrowseNext))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.BrowseNext(requestHeader, releaseContinuationPoint, continuationPoint, out revisedContinuationPoint, out references); } @@ -793,7 +913,7 @@ public ResponseHeader EndBrowseNext(IAsyncResult result, out byte[] revisedConti /// public IList Call(NodeId objectId, NodeId methodId, params object[] args) { - using (Activity activity = ActivitySource.StartActivity(nameof(Call))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.Call(objectId, methodId, args); } @@ -808,16 +928,25 @@ public IAsyncResult BeginPublish(int timeout) /// public bool Republish(uint subscriptionId, uint sequenceNumber) { - using (Activity activity = ActivitySource.StartActivity(nameof(Republish))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.Republish(subscriptionId, sequenceNumber); } } + /// + public async Task RepublishAsync(uint subscriptionId, uint sequenceNumber, CancellationToken ct = default) + { + using (Activity activity = ActivitySource.StartActivity()) + { + return await m_session.RepublishAsync(subscriptionId, sequenceNumber, ct).ConfigureAwait(false); + } + } + /// public ResponseHeader CreateSession(RequestHeader requestHeader, ApplicationDescription clientDescription, string serverUri, string endpointUrl, string sessionName, byte[] clientNonce, byte[] clientCertificate, double requestedSessionTimeout, uint maxResponseMessageSize, out NodeId sessionId, out NodeId authenticationToken, out double revisedSessionTimeout, out byte[] serverNonce, out byte[] serverCertificate, out EndpointDescriptionCollection serverEndpoints, out SignedSoftwareCertificateCollection serverSoftwareCertificates, out SignatureData serverSignature, out uint maxRequestMessageSize) { - using (Activity activity = ActivitySource.StartActivity(nameof(CreateSession))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.CreateSession(requestHeader, clientDescription, serverUri, endpointUrl, sessionName, clientNonce, clientCertificate, requestedSessionTimeout, maxResponseMessageSize, out sessionId, out authenticationToken, out revisedSessionTimeout, out serverNonce, out serverCertificate, out serverEndpoints, out serverSoftwareCertificates, out serverSignature, out maxRequestMessageSize); } @@ -838,7 +967,7 @@ public ResponseHeader EndCreateSession(IAsyncResult result, out NodeId sessionId /// public async Task CreateSessionAsync(RequestHeader requestHeader, ApplicationDescription clientDescription, string serverUri, string endpointUrl, string sessionName, byte[] clientNonce, byte[] clientCertificate, double requestedSessionTimeout, uint maxResponseMessageSize, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(CreateSessionAsync))) + using (Activity activity = ActivitySource.StartActivity()) { return await m_session.CreateSessionAsync(requestHeader, clientDescription, serverUri, endpointUrl, sessionName, clientNonce, clientCertificate, requestedSessionTimeout, maxResponseMessageSize, ct).ConfigureAwait(false); } @@ -847,7 +976,7 @@ public async Task CreateSessionAsync(RequestHeader reques /// public ResponseHeader ActivateSession(RequestHeader requestHeader, SignatureData clientSignature, SignedSoftwareCertificateCollection clientSoftwareCertificates, StringCollection localeIds, ExtensionObject userIdentityToken, SignatureData userTokenSignature, out byte[] serverNonce, out StatusCodeCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(ActivateSession))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.ActivateSession(requestHeader, clientSignature, clientSoftwareCertificates, localeIds, userIdentityToken, userTokenSignature, out serverNonce, out results, out diagnosticInfos); } @@ -868,7 +997,7 @@ public ResponseHeader EndActivateSession(IAsyncResult result, out byte[] serverN /// public async Task ActivateSessionAsync(RequestHeader requestHeader, SignatureData clientSignature, SignedSoftwareCertificateCollection clientSoftwareCertificates, StringCollection localeIds, ExtensionObject userIdentityToken, SignatureData userTokenSignature, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(ActivateSessionAsync))) + using (Activity activity = ActivitySource.StartActivity()) { return await m_session.ActivateSessionAsync(requestHeader, clientSignature, clientSoftwareCertificates, localeIds, userIdentityToken, userTokenSignature, ct).ConfigureAwait(false); } @@ -877,7 +1006,7 @@ public async Task ActivateSessionAsync(RequestHeader re /// public ResponseHeader CloseSession(RequestHeader requestHeader, bool deleteSubscriptions) { - using (Activity activity = ActivitySource.StartActivity(nameof(CloseSession))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.CloseSession(requestHeader, deleteSubscriptions); } @@ -898,7 +1027,7 @@ public ResponseHeader EndCloseSession(IAsyncResult result) /// public async Task CloseSessionAsync(RequestHeader requestHeader, bool deleteSubscriptions, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(CloseSessionAsync))) + using (Activity activity = ActivitySource.StartActivity()) { return await m_session.CloseSessionAsync(requestHeader, deleteSubscriptions, ct).ConfigureAwait(false); } @@ -907,7 +1036,7 @@ public async Task CloseSessionAsync(RequestHeader requestH /// public ResponseHeader Cancel(RequestHeader requestHeader, uint requestHandle, out uint cancelCount) { - using (Activity activity = ActivitySource.StartActivity(nameof(Cancel))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.Cancel(requestHeader, requestHandle, out cancelCount); } @@ -926,18 +1055,18 @@ public ResponseHeader EndCancel(IAsyncResult result, out uint cancelCount) } /// - public Task CancelAsync(RequestHeader requestHeader, uint requestHandle, CancellationToken ct) + public async Task CancelAsync(RequestHeader requestHeader, uint requestHandle, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(CancelAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.CancelAsync(requestHeader, requestHandle, ct); + return await m_session.CancelAsync(requestHeader, requestHandle, ct).ConfigureAwait(false); } } /// public ResponseHeader AddNodes(RequestHeader requestHeader, AddNodesItemCollection nodesToAdd, out AddNodesResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(AddNodes))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.AddNodes(requestHeader, nodesToAdd, out results, out diagnosticInfos); } @@ -956,18 +1085,18 @@ public ResponseHeader EndAddNodes(IAsyncResult result, out AddNodesResultCollect } /// - public Task AddNodesAsync(RequestHeader requestHeader, AddNodesItemCollection nodesToAdd, CancellationToken ct) + public async Task AddNodesAsync(RequestHeader requestHeader, AddNodesItemCollection nodesToAdd, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(AddNodesAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.AddNodesAsync(requestHeader, nodesToAdd, ct); + return await m_session.AddNodesAsync(requestHeader, nodesToAdd, ct).ConfigureAwait(false); } } /// public ResponseHeader AddReferences(RequestHeader requestHeader, AddReferencesItemCollection referencesToAdd, out StatusCodeCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(AddReferences))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.AddReferences(requestHeader, referencesToAdd, out results, out diagnosticInfos); } @@ -986,18 +1115,18 @@ public ResponseHeader EndAddReferences(IAsyncResult result, out StatusCodeCollec } /// - public Task AddReferencesAsync(RequestHeader requestHeader, AddReferencesItemCollection referencesToAdd, CancellationToken ct) + public async Task AddReferencesAsync(RequestHeader requestHeader, AddReferencesItemCollection referencesToAdd, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(AddReferencesAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.AddReferencesAsync(requestHeader, referencesToAdd, ct); + return await m_session.AddReferencesAsync(requestHeader, referencesToAdd, ct).ConfigureAwait(false); } } /// public ResponseHeader DeleteNodes(RequestHeader requestHeader, DeleteNodesItemCollection nodesToDelete, out StatusCodeCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(DeleteNodes))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.DeleteNodes(requestHeader, nodesToDelete, out results, out diagnosticInfos); } @@ -1016,18 +1145,18 @@ public ResponseHeader EndDeleteNodes(IAsyncResult result, out StatusCodeCollecti } /// - public Task DeleteNodesAsync(RequestHeader requestHeader, DeleteNodesItemCollection nodesToDelete, CancellationToken ct) + public async Task DeleteNodesAsync(RequestHeader requestHeader, DeleteNodesItemCollection nodesToDelete, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(DeleteNodesAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.DeleteNodesAsync(requestHeader, nodesToDelete, ct); + return await m_session.DeleteNodesAsync(requestHeader, nodesToDelete, ct).ConfigureAwait(false); } } /// public ResponseHeader DeleteReferences(RequestHeader requestHeader, DeleteReferencesItemCollection referencesToDelete, out StatusCodeCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(DeleteReferences))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.DeleteReferences(requestHeader, referencesToDelete, out results, out diagnosticInfos); } @@ -1046,18 +1175,18 @@ public ResponseHeader EndDeleteReferences(IAsyncResult result, out StatusCodeCol } /// - public Task DeleteReferencesAsync(RequestHeader requestHeader, DeleteReferencesItemCollection referencesToDelete, CancellationToken ct) + public async Task DeleteReferencesAsync(RequestHeader requestHeader, DeleteReferencesItemCollection referencesToDelete, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(DeleteReferencesAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.DeleteReferencesAsync(requestHeader, referencesToDelete, ct); + return await m_session.DeleteReferencesAsync(requestHeader, referencesToDelete, ct).ConfigureAwait(false); } } /// public ResponseHeader Browse(RequestHeader requestHeader, ViewDescription view, uint requestedMaxReferencesPerNode, BrowseDescriptionCollection nodesToBrowse, out BrowseResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(Browse))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.Browse(requestHeader, view, requestedMaxReferencesPerNode, nodesToBrowse, out results, out diagnosticInfos); } @@ -1076,18 +1205,18 @@ public ResponseHeader EndBrowse(IAsyncResult result, out BrowseResultCollection } /// - public Task BrowseAsync(RequestHeader requestHeader, ViewDescription view, uint requestedMaxReferencesPerNode, BrowseDescriptionCollection nodesToBrowse, CancellationToken ct) + public async Task BrowseAsync(RequestHeader requestHeader, ViewDescription view, uint requestedMaxReferencesPerNode, BrowseDescriptionCollection nodesToBrowse, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(BrowseAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.BrowseAsync(requestHeader, view, requestedMaxReferencesPerNode, nodesToBrowse, ct); + return await m_session.BrowseAsync(requestHeader, view, requestedMaxReferencesPerNode, nodesToBrowse, ct).ConfigureAwait(false); } } /// public ResponseHeader BrowseNext(RequestHeader requestHeader, bool releaseContinuationPoints, ByteStringCollection continuationPoints, out BrowseResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(BrowseNext))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.BrowseNext(requestHeader, releaseContinuationPoints, continuationPoints, out results, out diagnosticInfos); } @@ -1106,18 +1235,18 @@ public ResponseHeader EndBrowseNext(IAsyncResult result, out BrowseResultCollect } /// - public Task BrowseNextAsync(RequestHeader requestHeader, bool releaseContinuationPoints, ByteStringCollection continuationPoints, CancellationToken ct) + public async Task BrowseNextAsync(RequestHeader requestHeader, bool releaseContinuationPoints, ByteStringCollection continuationPoints, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(BrowseNextAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.BrowseNextAsync(requestHeader, releaseContinuationPoints, continuationPoints, ct); + return await m_session.BrowseNextAsync(requestHeader, releaseContinuationPoints, continuationPoints, ct).ConfigureAwait(false); } } /// public ResponseHeader TranslateBrowsePathsToNodeIds(RequestHeader requestHeader, BrowsePathCollection browsePaths, out BrowsePathResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(TranslateBrowsePathsToNodeIds))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.TranslateBrowsePathsToNodeIds(requestHeader, browsePaths, out results, out diagnosticInfos); } @@ -1136,18 +1265,18 @@ public ResponseHeader EndTranslateBrowsePathsToNodeIds(IAsyncResult result, out } /// - public Task TranslateBrowsePathsToNodeIdsAsync(RequestHeader requestHeader, BrowsePathCollection browsePaths, CancellationToken ct) + public async Task TranslateBrowsePathsToNodeIdsAsync(RequestHeader requestHeader, BrowsePathCollection browsePaths, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(TranslateBrowsePathsToNodeIdsAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.TranslateBrowsePathsToNodeIdsAsync(requestHeader, browsePaths, ct); + return await m_session.TranslateBrowsePathsToNodeIdsAsync(requestHeader, browsePaths, ct).ConfigureAwait(false); } } /// public ResponseHeader RegisterNodes(RequestHeader requestHeader, NodeIdCollection nodesToRegister, out NodeIdCollection registeredNodeIds) { - using (Activity activity = ActivitySource.StartActivity(nameof(RegisterNodes))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.RegisterNodes(requestHeader, nodesToRegister, out registeredNodeIds); } @@ -1166,18 +1295,18 @@ public ResponseHeader EndRegisterNodes(IAsyncResult result, out NodeIdCollection } /// - public Task RegisterNodesAsync(RequestHeader requestHeader, NodeIdCollection nodesToRegister, CancellationToken ct) + public async Task RegisterNodesAsync(RequestHeader requestHeader, NodeIdCollection nodesToRegister, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(RegisterNodesAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.RegisterNodesAsync(requestHeader, nodesToRegister, ct); + return await m_session.RegisterNodesAsync(requestHeader, nodesToRegister, ct).ConfigureAwait(false); } } /// public ResponseHeader UnregisterNodes(RequestHeader requestHeader, NodeIdCollection nodesToUnregister) { - using (Activity activity = ActivitySource.StartActivity(nameof(UnregisterNodes))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.UnregisterNodes(requestHeader, nodesToUnregister); } @@ -1196,18 +1325,18 @@ public ResponseHeader EndUnregisterNodes(IAsyncResult result) } /// - public Task UnregisterNodesAsync(RequestHeader requestHeader, NodeIdCollection nodesToUnregister, CancellationToken ct) + public async Task UnregisterNodesAsync(RequestHeader requestHeader, NodeIdCollection nodesToUnregister, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(UnregisterNodesAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.UnregisterNodesAsync(requestHeader, nodesToUnregister, ct); + return await m_session.UnregisterNodesAsync(requestHeader, nodesToUnregister, ct).ConfigureAwait(false); } } /// public ResponseHeader QueryFirst(RequestHeader requestHeader, ViewDescription view, NodeTypeDescriptionCollection nodeTypes, ContentFilter filter, uint maxDataSetsToReturn, uint maxReferencesToReturn, out QueryDataSetCollection queryDataSets, out byte[] continuationPoint, out ParsingResultCollection parsingResults, out DiagnosticInfoCollection diagnosticInfos, out ContentFilterResult filterResult) { - using (Activity activity = ActivitySource.StartActivity(nameof(QueryFirst))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.QueryFirst(requestHeader, view, nodeTypes, filter, maxDataSetsToReturn, maxReferencesToReturn, out queryDataSets, out continuationPoint, out parsingResults, out diagnosticInfos, out filterResult); } @@ -1226,18 +1355,18 @@ public ResponseHeader EndQueryFirst(IAsyncResult result, out QueryDataSetCollect } /// - public Task QueryFirstAsync(RequestHeader requestHeader, ViewDescription view, NodeTypeDescriptionCollection nodeTypes, ContentFilter filter, uint maxDataSetsToReturn, uint maxReferencesToReturn, CancellationToken ct) + public async Task QueryFirstAsync(RequestHeader requestHeader, ViewDescription view, NodeTypeDescriptionCollection nodeTypes, ContentFilter filter, uint maxDataSetsToReturn, uint maxReferencesToReturn, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(QueryFirstAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.QueryFirstAsync(requestHeader, view, nodeTypes, filter, maxDataSetsToReturn, maxReferencesToReturn, ct); + return await m_session.QueryFirstAsync(requestHeader, view, nodeTypes, filter, maxDataSetsToReturn, maxReferencesToReturn, ct).ConfigureAwait(false); } } /// public ResponseHeader QueryNext(RequestHeader requestHeader, bool releaseContinuationPoint, byte[] continuationPoint, out QueryDataSetCollection queryDataSets, out byte[] revisedContinuationPoint) { - using (Activity activity = ActivitySource.StartActivity(nameof(QueryNext))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.QueryNext(requestHeader, releaseContinuationPoint, continuationPoint, out queryDataSets, out revisedContinuationPoint); } @@ -1256,18 +1385,18 @@ public ResponseHeader EndQueryNext(IAsyncResult result, out QueryDataSetCollecti } /// - public Task QueryNextAsync(RequestHeader requestHeader, bool releaseContinuationPoint, byte[] continuationPoint, CancellationToken ct) + public async Task QueryNextAsync(RequestHeader requestHeader, bool releaseContinuationPoint, byte[] continuationPoint, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(QueryNextAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.QueryNextAsync(requestHeader, releaseContinuationPoint, continuationPoint, ct); + return await m_session.QueryNextAsync(requestHeader, releaseContinuationPoint, continuationPoint, ct).ConfigureAwait(false); } } /// public ResponseHeader Read(RequestHeader requestHeader, double maxAge, TimestampsToReturn timestampsToReturn, ReadValueIdCollection nodesToRead, out DataValueCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(Read))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.Read(requestHeader, maxAge, timestampsToReturn, nodesToRead, out results, out diagnosticInfos); } @@ -1286,18 +1415,18 @@ public ResponseHeader EndRead(IAsyncResult result, out DataValueCollection resul } /// - public Task ReadAsync(RequestHeader requestHeader, double maxAge, TimestampsToReturn timestampsToReturn, ReadValueIdCollection nodesToRead, CancellationToken ct) + public async Task ReadAsync(RequestHeader requestHeader, double maxAge, TimestampsToReturn timestampsToReturn, ReadValueIdCollection nodesToRead, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReadAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.ReadAsync(requestHeader, maxAge, timestampsToReturn, nodesToRead, ct); + return await m_session.ReadAsync(requestHeader, maxAge, timestampsToReturn, nodesToRead, ct).ConfigureAwait(false); } } /// public ResponseHeader HistoryRead(RequestHeader requestHeader, ExtensionObject historyReadDetails, TimestampsToReturn timestampsToReturn, bool releaseContinuationPoints, HistoryReadValueIdCollection nodesToRead, out HistoryReadResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(HistoryRead))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.HistoryRead(requestHeader, historyReadDetails, timestampsToReturn, releaseContinuationPoints, nodesToRead, out results, out diagnosticInfos); } @@ -1316,18 +1445,18 @@ public ResponseHeader EndHistoryRead(IAsyncResult result, out HistoryReadResultC } /// - public Task HistoryReadAsync(RequestHeader requestHeader, ExtensionObject historyReadDetails, TimestampsToReturn timestampsToReturn, bool releaseContinuationPoints, HistoryReadValueIdCollection nodesToRead, CancellationToken ct) + public async Task HistoryReadAsync(RequestHeader requestHeader, ExtensionObject historyReadDetails, TimestampsToReturn timestampsToReturn, bool releaseContinuationPoints, HistoryReadValueIdCollection nodesToRead, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(HistoryReadAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.HistoryReadAsync(requestHeader, historyReadDetails, timestampsToReturn, releaseContinuationPoints, nodesToRead, ct); + return await m_session.HistoryReadAsync(requestHeader, historyReadDetails, timestampsToReturn, releaseContinuationPoints, nodesToRead, ct).ConfigureAwait(false); } } /// public ResponseHeader Write(RequestHeader requestHeader, WriteValueCollection nodesToWrite, out StatusCodeCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(Write))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.Write(requestHeader, nodesToWrite, out results, out diagnosticInfos); } @@ -1346,18 +1475,18 @@ public ResponseHeader EndWrite(IAsyncResult result, out StatusCodeCollection res } /// - public Task WriteAsync(RequestHeader requestHeader, WriteValueCollection nodesToWrite, CancellationToken ct) + public async Task WriteAsync(RequestHeader requestHeader, WriteValueCollection nodesToWrite, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(WriteAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.WriteAsync(requestHeader, nodesToWrite, ct); + return await m_session.WriteAsync(requestHeader, nodesToWrite, ct).ConfigureAwait(false); } } /// public ResponseHeader HistoryUpdate(RequestHeader requestHeader, ExtensionObjectCollection historyUpdateDetails, out HistoryUpdateResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(HistoryUpdate))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.HistoryUpdate(requestHeader, historyUpdateDetails, out results, out diagnosticInfos); } @@ -1376,18 +1505,18 @@ public ResponseHeader EndHistoryUpdate(IAsyncResult result, out HistoryUpdateRes } /// - public Task HistoryUpdateAsync(RequestHeader requestHeader, ExtensionObjectCollection historyUpdateDetails, CancellationToken ct) + public async Task HistoryUpdateAsync(RequestHeader requestHeader, ExtensionObjectCollection historyUpdateDetails, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(HistoryUpdateAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.HistoryUpdateAsync(requestHeader, historyUpdateDetails, ct); + return await m_session.HistoryUpdateAsync(requestHeader, historyUpdateDetails, ct).ConfigureAwait(false); } } /// public ResponseHeader Call(RequestHeader requestHeader, CallMethodRequestCollection methodsToCall, out CallMethodResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(Call))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.Call(requestHeader, methodsToCall, out results, out diagnosticInfos); } @@ -1406,18 +1535,18 @@ public ResponseHeader EndCall(IAsyncResult result, out CallMethodResultCollectio } /// - public Task CallAsync(RequestHeader requestHeader, CallMethodRequestCollection methodsToCall, CancellationToken ct) + public async Task CallAsync(RequestHeader requestHeader, CallMethodRequestCollection methodsToCall, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(CallAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.CallAsync(requestHeader, methodsToCall, ct); + return await m_session.CallAsync(requestHeader, methodsToCall, ct).ConfigureAwait(false); } } /// public ResponseHeader CreateMonitoredItems(RequestHeader requestHeader, uint subscriptionId, TimestampsToReturn timestampsToReturn, MonitoredItemCreateRequestCollection itemsToCreate, out MonitoredItemCreateResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(CreateMonitoredItems))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.CreateMonitoredItems(requestHeader, subscriptionId, timestampsToReturn, itemsToCreate, out results, out diagnosticInfos); } @@ -1436,18 +1565,18 @@ public ResponseHeader EndCreateMonitoredItems(IAsyncResult result, out Monitored } /// - public Task CreateMonitoredItemsAsync(RequestHeader requestHeader, uint subscriptionId, TimestampsToReturn timestampsToReturn, MonitoredItemCreateRequestCollection itemsToCreate, CancellationToken ct) + public async Task CreateMonitoredItemsAsync(RequestHeader requestHeader, uint subscriptionId, TimestampsToReturn timestampsToReturn, MonitoredItemCreateRequestCollection itemsToCreate, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(CreateMonitoredItemsAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.CreateMonitoredItemsAsync(requestHeader, subscriptionId, timestampsToReturn, itemsToCreate, ct); + return await m_session.CreateMonitoredItemsAsync(requestHeader, subscriptionId, timestampsToReturn, itemsToCreate, ct).ConfigureAwait(false); } } /// public ResponseHeader ModifyMonitoredItems(RequestHeader requestHeader, uint subscriptionId, TimestampsToReturn timestampsToReturn, MonitoredItemModifyRequestCollection itemsToModify, out MonitoredItemModifyResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(ModifyMonitoredItems))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.ModifyMonitoredItems(requestHeader, subscriptionId, timestampsToReturn, itemsToModify, out results, out diagnosticInfos); } @@ -1466,18 +1595,18 @@ public ResponseHeader EndModifyMonitoredItems(IAsyncResult result, out Monitored } /// - public Task ModifyMonitoredItemsAsync(RequestHeader requestHeader, uint subscriptionId, TimestampsToReturn timestampsToReturn, MonitoredItemModifyRequestCollection itemsToModify, CancellationToken ct) + public async Task ModifyMonitoredItemsAsync(RequestHeader requestHeader, uint subscriptionId, TimestampsToReturn timestampsToReturn, MonitoredItemModifyRequestCollection itemsToModify, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(ModifyMonitoredItemsAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.ModifyMonitoredItemsAsync(requestHeader, subscriptionId, timestampsToReturn, itemsToModify, ct); + return await m_session.ModifyMonitoredItemsAsync(requestHeader, subscriptionId, timestampsToReturn, itemsToModify, ct).ConfigureAwait(false); } } /// public ResponseHeader SetMonitoringMode(RequestHeader requestHeader, uint subscriptionId, MonitoringMode monitoringMode, UInt32Collection monitoredItemIds, out StatusCodeCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(SetMonitoringMode))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.SetMonitoringMode(requestHeader, subscriptionId, monitoringMode, monitoredItemIds, out results, out diagnosticInfos); } @@ -1496,18 +1625,18 @@ public ResponseHeader EndSetMonitoringMode(IAsyncResult result, out StatusCodeCo } /// - public Task SetMonitoringModeAsync(RequestHeader requestHeader, uint subscriptionId, MonitoringMode monitoringMode, UInt32Collection monitoredItemIds, CancellationToken ct) + public async Task SetMonitoringModeAsync(RequestHeader requestHeader, uint subscriptionId, MonitoringMode monitoringMode, UInt32Collection monitoredItemIds, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(SetMonitoringModeAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.SetMonitoringModeAsync(requestHeader, subscriptionId, monitoringMode, monitoredItemIds, ct); + return await m_session.SetMonitoringModeAsync(requestHeader, subscriptionId, monitoringMode, monitoredItemIds, ct).ConfigureAwait(false); } } /// public ResponseHeader SetTriggering(RequestHeader requestHeader, uint subscriptionId, uint triggeringItemId, UInt32Collection linksToAdd, UInt32Collection linksToRemove, out StatusCodeCollection addResults, out DiagnosticInfoCollection addDiagnosticInfos, out StatusCodeCollection removeResults, out DiagnosticInfoCollection removeDiagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(SetTriggering))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.SetTriggering(requestHeader, subscriptionId, triggeringItemId, linksToAdd, linksToRemove, out addResults, out addDiagnosticInfos, out removeResults, out removeDiagnosticInfos); } @@ -1526,18 +1655,18 @@ public ResponseHeader EndSetTriggering(IAsyncResult result, out StatusCodeCollec } /// - public Task SetTriggeringAsync(RequestHeader requestHeader, uint subscriptionId, uint triggeringItemId, UInt32Collection linksToAdd, UInt32Collection linksToRemove, CancellationToken ct) + public async Task SetTriggeringAsync(RequestHeader requestHeader, uint subscriptionId, uint triggeringItemId, UInt32Collection linksToAdd, UInt32Collection linksToRemove, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(SetTriggeringAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.SetTriggeringAsync(requestHeader, subscriptionId, triggeringItemId, linksToAdd, linksToRemove, ct); + return await m_session.SetTriggeringAsync(requestHeader, subscriptionId, triggeringItemId, linksToAdd, linksToRemove, ct).ConfigureAwait(false); } } /// public ResponseHeader DeleteMonitoredItems(RequestHeader requestHeader, uint subscriptionId, UInt32Collection monitoredItemIds, out StatusCodeCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(DeleteMonitoredItems))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.DeleteMonitoredItems(requestHeader, subscriptionId, monitoredItemIds, out results, out diagnosticInfos); } @@ -1556,18 +1685,18 @@ public ResponseHeader EndDeleteMonitoredItems(IAsyncResult result, out StatusCod } /// - public Task DeleteMonitoredItemsAsync(RequestHeader requestHeader, uint subscriptionId, UInt32Collection monitoredItemIds, CancellationToken ct) + public async Task DeleteMonitoredItemsAsync(RequestHeader requestHeader, uint subscriptionId, UInt32Collection monitoredItemIds, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(DeleteMonitoredItemsAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.DeleteMonitoredItemsAsync(requestHeader, subscriptionId, monitoredItemIds, ct); + return await m_session.DeleteMonitoredItemsAsync(requestHeader, subscriptionId, monitoredItemIds, ct).ConfigureAwait(false); } } /// public ResponseHeader CreateSubscription(RequestHeader requestHeader, double requestedPublishingInterval, uint requestedLifetimeCount, uint requestedMaxKeepAliveCount, uint maxNotificationsPerPublish, bool publishingEnabled, byte priority, out uint subscriptionId, out double revisedPublishingInterval, out uint revisedLifetimeCount, out uint revisedMaxKeepAliveCount) { - using (Activity activity = ActivitySource.StartActivity(nameof(CreateSubscription))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.CreateSubscription(requestHeader, requestedPublishingInterval, requestedLifetimeCount, requestedMaxKeepAliveCount, maxNotificationsPerPublish, publishingEnabled, priority, out subscriptionId, out revisedPublishingInterval, out revisedLifetimeCount, out revisedMaxKeepAliveCount); } @@ -1586,18 +1715,18 @@ public ResponseHeader EndCreateSubscription(IAsyncResult result, out uint subscr } /// - public Task CreateSubscriptionAsync(RequestHeader requestHeader, double requestedPublishingInterval, uint requestedLifetimeCount, uint requestedMaxKeepAliveCount, uint maxNotificationsPerPublish, bool publishingEnabled, byte priority, CancellationToken ct) + public async Task CreateSubscriptionAsync(RequestHeader requestHeader, double requestedPublishingInterval, uint requestedLifetimeCount, uint requestedMaxKeepAliveCount, uint maxNotificationsPerPublish, bool publishingEnabled, byte priority, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(CreateSubscriptionAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.CreateSubscriptionAsync(requestHeader, requestedPublishingInterval, requestedLifetimeCount, requestedMaxKeepAliveCount, maxNotificationsPerPublish, publishingEnabled, priority, ct); + return await m_session.CreateSubscriptionAsync(requestHeader, requestedPublishingInterval, requestedLifetimeCount, requestedMaxKeepAliveCount, maxNotificationsPerPublish, publishingEnabled, priority, ct).ConfigureAwait(false); } } /// public ResponseHeader ModifySubscription(RequestHeader requestHeader, uint subscriptionId, double requestedPublishingInterval, uint requestedLifetimeCount, uint requestedMaxKeepAliveCount, uint maxNotificationsPerPublish, byte priority, out double revisedPublishingInterval, out uint revisedLifetimeCount, out uint revisedMaxKeepAliveCount) { - using (Activity activity = ActivitySource.StartActivity(nameof(ModifySubscription))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.ModifySubscription(requestHeader, subscriptionId, requestedPublishingInterval, requestedLifetimeCount, requestedMaxKeepAliveCount, maxNotificationsPerPublish, priority, out revisedPublishingInterval, out revisedLifetimeCount, out revisedMaxKeepAliveCount); } @@ -1616,18 +1745,18 @@ public ResponseHeader EndModifySubscription(IAsyncResult result, out double revi } /// - public Task ModifySubscriptionAsync(RequestHeader requestHeader, uint subscriptionId, double requestedPublishingInterval, uint requestedLifetimeCount, uint requestedMaxKeepAliveCount, uint maxNotificationsPerPublish, byte priority, CancellationToken ct) + public async Task ModifySubscriptionAsync(RequestHeader requestHeader, uint subscriptionId, double requestedPublishingInterval, uint requestedLifetimeCount, uint requestedMaxKeepAliveCount, uint maxNotificationsPerPublish, byte priority, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(ModifySubscriptionAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.ModifySubscriptionAsync(requestHeader, subscriptionId, requestedPublishingInterval, requestedLifetimeCount, requestedMaxKeepAliveCount, maxNotificationsPerPublish, priority, ct); + return await m_session.ModifySubscriptionAsync(requestHeader, subscriptionId, requestedPublishingInterval, requestedLifetimeCount, requestedMaxKeepAliveCount, maxNotificationsPerPublish, priority, ct).ConfigureAwait(false); } } /// public ResponseHeader SetPublishingMode(RequestHeader requestHeader, bool publishingEnabled, UInt32Collection subscriptionIds, out StatusCodeCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(SetPublishingMode))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.SetPublishingMode(requestHeader, publishingEnabled, subscriptionIds, out results, out diagnosticInfos); } @@ -1646,18 +1775,18 @@ public ResponseHeader EndSetPublishingMode(IAsyncResult result, out StatusCodeCo } /// - public Task SetPublishingModeAsync(RequestHeader requestHeader, bool publishingEnabled, UInt32Collection subscriptionIds, CancellationToken ct) + public async Task SetPublishingModeAsync(RequestHeader requestHeader, bool publishingEnabled, UInt32Collection subscriptionIds, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(SetPublishingModeAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.SetPublishingModeAsync(requestHeader, publishingEnabled, subscriptionIds, ct); + return await m_session.SetPublishingModeAsync(requestHeader, publishingEnabled, subscriptionIds, ct).ConfigureAwait(false); } } /// public ResponseHeader Publish(RequestHeader requestHeader, SubscriptionAcknowledgementCollection subscriptionAcknowledgements, out uint subscriptionId, out UInt32Collection availableSequenceNumbers, out bool moreNotifications, out NotificationMessage notificationMessage, out StatusCodeCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(Publish))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.Publish(requestHeader, subscriptionAcknowledgements, out subscriptionId, out availableSequenceNumbers, out moreNotifications, out notificationMessage, out results, out diagnosticInfos); } @@ -1676,18 +1805,18 @@ public ResponseHeader EndPublish(IAsyncResult result, out uint subscriptionId, o } /// - public Task PublishAsync(RequestHeader requestHeader, SubscriptionAcknowledgementCollection subscriptionAcknowledgements, CancellationToken ct) + public async Task PublishAsync(RequestHeader requestHeader, SubscriptionAcknowledgementCollection subscriptionAcknowledgements, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(PublishAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.PublishAsync(requestHeader, subscriptionAcknowledgements, ct); + return await m_session.PublishAsync(requestHeader, subscriptionAcknowledgements, ct).ConfigureAwait(false); } } /// public ResponseHeader Republish(RequestHeader requestHeader, uint subscriptionId, uint retransmitSequenceNumber, out NotificationMessage notificationMessage) { - using (Activity activity = ActivitySource.StartActivity(nameof(Republish))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.Republish(requestHeader, subscriptionId, retransmitSequenceNumber, out notificationMessage); } @@ -1706,18 +1835,18 @@ public ResponseHeader EndRepublish(IAsyncResult result, out NotificationMessage } /// - public Task RepublishAsync(RequestHeader requestHeader, uint subscriptionId, uint retransmitSequenceNumber, CancellationToken ct) + public async Task RepublishAsync(RequestHeader requestHeader, uint subscriptionId, uint retransmitSequenceNumber, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(RepublishAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.RepublishAsync(requestHeader, subscriptionId, retransmitSequenceNumber, ct); + return await m_session.RepublishAsync(requestHeader, subscriptionId, retransmitSequenceNumber, ct).ConfigureAwait(false); } } /// public ResponseHeader TransferSubscriptions(RequestHeader requestHeader, UInt32Collection subscriptionIds, bool sendInitialValues, out TransferResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(TransferSubscriptions))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.TransferSubscriptions(requestHeader, subscriptionIds, sendInitialValues, out results, out diagnosticInfos); } @@ -1736,18 +1865,18 @@ public ResponseHeader EndTransferSubscriptions(IAsyncResult result, out Transfer } /// - public Task TransferSubscriptionsAsync(RequestHeader requestHeader, UInt32Collection subscriptionIds, bool sendInitialValues, CancellationToken ct) + public async Task TransferSubscriptionsAsync(RequestHeader requestHeader, UInt32Collection subscriptionIds, bool sendInitialValues, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(TransferSubscriptionsAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.TransferSubscriptionsAsync(requestHeader, subscriptionIds, sendInitialValues, ct); + return await m_session.TransferSubscriptionsAsync(requestHeader, subscriptionIds, sendInitialValues, ct).ConfigureAwait(false); } } /// public ResponseHeader DeleteSubscriptions(RequestHeader requestHeader, UInt32Collection subscriptionIds, out StatusCodeCollection results, out DiagnosticInfoCollection diagnosticInfos) { - using (Activity activity = ActivitySource.StartActivity(nameof(DeleteSubscriptions))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.DeleteSubscriptions(requestHeader, subscriptionIds, out results, out diagnosticInfos); } @@ -1766,18 +1895,18 @@ public ResponseHeader EndDeleteSubscriptions(IAsyncResult result, out StatusCode } /// - public Task DeleteSubscriptionsAsync(RequestHeader requestHeader, UInt32Collection subscriptionIds, CancellationToken ct) + public async Task DeleteSubscriptionsAsync(RequestHeader requestHeader, UInt32Collection subscriptionIds, CancellationToken ct) { - using (Activity activity = ActivitySource.StartActivity(nameof(DeleteSubscriptionsAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.DeleteSubscriptionsAsync(requestHeader, subscriptionIds, ct); + return await m_session.DeleteSubscriptionsAsync(requestHeader, subscriptionIds, ct).ConfigureAwait(false); } } /// public void AttachChannel(ITransportChannel channel) { - using (Activity activity = ActivitySource.StartActivity(nameof(AttachChannel))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.AttachChannel(channel); } @@ -1786,7 +1915,7 @@ public void AttachChannel(ITransportChannel channel) /// public void DetachChannel() { - using (Activity activity = ActivitySource.StartActivity(nameof(DetachChannel))) + using (Activity activity = ActivitySource.StartActivity()) { m_session.DetachChannel(); } @@ -1795,7 +1924,7 @@ public void DetachChannel() /// public StatusCode Close() { - using (Activity activity = ActivitySource.StartActivity(nameof(Close))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.Close(); } @@ -1831,7 +1960,7 @@ public void Dispose() /// public SessionConfiguration SaveSessionConfiguration(Stream stream = null) { - using (Activity activity = ActivitySource.StartActivity(nameof(SaveSessionConfiguration))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.SaveSessionConfiguration(stream); } @@ -1840,7 +1969,7 @@ public SessionConfiguration SaveSessionConfiguration(Stream stream = null) /// public bool ApplySessionConfiguration(SessionConfiguration sessionConfiguration) { - using (Activity activity = ActivitySource.StartActivity(nameof(ApplySessionConfiguration))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.ApplySessionConfiguration(sessionConfiguration); } @@ -1849,72 +1978,72 @@ public bool ApplySessionConfiguration(SessionConfiguration sessionConfiguration) /// public bool ReactivateSubscriptions(SubscriptionCollection subscriptions, bool sendInitialValues) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReactivateSubscriptions))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.ReactivateSubscriptions(subscriptions, sendInitialValues); } } /// - public Task RemoveSubscriptionAsync(Subscription subscription, CancellationToken ct = default) + public async Task RemoveSubscriptionAsync(Subscription subscription, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(RemoveSubscriptionAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.RemoveSubscriptionAsync(subscription, ct); + return await m_session.RemoveSubscriptionAsync(subscription, ct).ConfigureAwait(false); } } /// - public Task RemoveSubscriptionsAsync(IEnumerable subscriptions, CancellationToken ct = default) + public async Task RemoveSubscriptionsAsync(IEnumerable subscriptions, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(RemoveSubscriptionsAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.RemoveSubscriptionsAsync(subscriptions, ct); + return await m_session.RemoveSubscriptionsAsync(subscriptions, ct).ConfigureAwait(false); } } /// - public Task ReactivateSubscriptionsAsync(SubscriptionCollection subscriptions, bool sendInitialValues, CancellationToken ct = default) + public async Task ReactivateSubscriptionsAsync(SubscriptionCollection subscriptions, bool sendInitialValues, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(ReactivateSubscriptionsAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.ReactivateSubscriptionsAsync(subscriptions, sendInitialValues, ct); + return await m_session.ReactivateSubscriptionsAsync(subscriptions, sendInitialValues, ct).ConfigureAwait(false); } } /// - public Task TransferSubscriptionsAsync(SubscriptionCollection subscriptions, bool sendInitialValues, CancellationToken ct = default) + public async Task TransferSubscriptionsAsync(SubscriptionCollection subscriptions, bool sendInitialValues, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(TransferSubscriptionsAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.TransferSubscriptionsAsync(subscriptions, sendInitialValues, ct); + return await m_session.TransferSubscriptionsAsync(subscriptions, sendInitialValues, ct).ConfigureAwait(false); } } /// - public Task> CallAsync(NodeId objectId, NodeId methodId, CancellationToken ct = default, params object[] args) + public async Task> CallAsync(NodeId objectId, NodeId methodId, CancellationToken ct = default, params object[] args) { - using (Activity activity = ActivitySource.StartActivity(nameof(CallAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.CallAsync(objectId, methodId, ct, args); + return await m_session.CallAsync(objectId, methodId, ct, args).ConfigureAwait(false); } } /// public bool ResendData(IEnumerable subscriptions, out IList errors) { - using (Activity activity = ActivitySource.StartActivity(nameof(ResendData))) + using (Activity activity = ActivitySource.StartActivity()) { return m_session.ResendData(subscriptions, out errors); } } /// - public Task<(bool, IList)> ResendDataAsync(IEnumerable subscriptions, CancellationToken ct = default) + public async Task<(bool, IList)> ResendDataAsync(IEnumerable subscriptions, CancellationToken ct = default) { - using (Activity activity = ActivitySource.StartActivity(nameof(ResendDataAsync))) + using (Activity activity = ActivitySource.StartActivity()) { - return m_session.ResendDataAsync(subscriptions, ct); + return await m_session.ResendDataAsync(subscriptions, ct).ConfigureAwait(false); } } #endregion diff --git a/Libraries/Opc.Ua.Client/TraceableSessionFactory.cs b/Libraries/Opc.Ua.Client/TraceableSessionFactory.cs index d2030065b..486deeda9 100644 --- a/Libraries/Opc.Ua.Client/TraceableSessionFactory.cs +++ b/Libraries/Opc.Ua.Client/TraceableSessionFactory.cs @@ -65,11 +65,10 @@ public override async Task CreateAsync( IList preferredLocales, CancellationToken ct = default) { - using (Activity activity = TraceableSession.ActivitySource.StartActivity(nameof(CreateAsync))) + using (Activity activity = TraceableSession.ActivitySource.StartActivity()) { - ISession session = await Session.Create(configuration, endpoint, updateBeforeConnect, false, + ISession session = await base.CreateAsync(configuration, endpoint, updateBeforeConnect, false, sessionName, sessionTimeout, identity, preferredLocales, ct).ConfigureAwait(false); - return new TraceableSession(session); } } @@ -86,9 +85,9 @@ public override async Task CreateAsync( IList preferredLocales, CancellationToken ct = default) { - using (Activity activity = TraceableSession.ActivitySource.StartActivity(nameof(CreateAsync))) + using (Activity activity = TraceableSession.ActivitySource.StartActivity()) { - ISession session = await Session.Create(configuration, (ITransportWaitingConnection)null, endpoint, + ISession session = await Session.Create(this, configuration, (ITransportWaitingConnection)null, endpoint, updateBeforeConnect, checkDomain, sessionName, sessionTimeout, identity, preferredLocales, ct).ConfigureAwait(false); @@ -109,9 +108,9 @@ public override async Task CreateAsync( IList preferredLocales, CancellationToken ct = default) { - using (Activity activity = TraceableSession.ActivitySource.StartActivity(nameof(CreateAsync))) + using (Activity activity = TraceableSession.ActivitySource.StartActivity()) { - ISession session = await Session.Create(configuration, connection, endpoint, + ISession session = await Session.Create(this, configuration, connection, endpoint, updateBeforeConnect, checkDomain, sessionName, sessionTimeout, identity, preferredLocales, ct ).ConfigureAwait(false); @@ -129,14 +128,14 @@ public override ISession Create( EndpointDescriptionCollection availableEndpoints = null, StringCollection discoveryProfileUris = null) { - using (Activity activity = TraceableSession.ActivitySource.StartActivity(nameof(CreateAsync))) + using (Activity activity = TraceableSession.ActivitySource.StartActivity()) { return new TraceableSession(base.Create(configuration, channel, endpoint, clientCertificate, availableEndpoints, discoveryProfileUris)); } } /// - public override Task CreateChannelAsync( + public override async Task CreateChannelAsync( ApplicationConfiguration configuration, ITransportWaitingConnection connection, ConfiguredEndpoint endpoint, @@ -144,9 +143,9 @@ public override Task CreateChannelAsync( bool checkDomain, CancellationToken ct = default) { - using (Activity activity = TraceableSession.ActivitySource.StartActivity(nameof(CreateAsync))) + using (Activity activity = TraceableSession.ActivitySource.StartActivity()) { - return base.CreateChannelAsync(configuration, connection, endpoint, updateBeforeConnect, checkDomain, ct); + return await base.CreateChannelAsync(configuration, connection, endpoint, updateBeforeConnect, checkDomain, ct).ConfigureAwait(false); } } @@ -164,7 +163,7 @@ public override async Task CreateAsync( CancellationToken ct = default ) { - using (Activity activity = TraceableSession.ActivitySource.StartActivity(nameof(CreateAsync))) + using (Activity activity = TraceableSession.ActivitySource.StartActivity()) { ISession session = await base.CreateAsync(configuration, reverseConnectManager, endpoint, @@ -178,32 +177,32 @@ public override async Task CreateAsync( } /// - public override Task RecreateAsync(ISession sessionTemplate, CancellationToken ct = default) + public override async Task RecreateAsync(ISession sessionTemplate, CancellationToken ct = default) { Session session = ValidateISession(sessionTemplate); - using (Activity activity = TraceableSession.ActivitySource.StartActivity(nameof(RecreateAsync))) + using (Activity activity = TraceableSession.ActivitySource.StartActivity()) { - return Task.FromResult((ISession)new TraceableSession(Session.Recreate(session))); + return new TraceableSession(await Session.RecreateAsync(session, ct).ConfigureAwait(false)); } } /// - public override Task RecreateAsync(ISession sessionTemplate, ITransportWaitingConnection connection, CancellationToken ct = default) + public override async Task RecreateAsync(ISession sessionTemplate, ITransportWaitingConnection connection, CancellationToken ct = default) { Session session = ValidateISession(sessionTemplate); - using (Activity activity = TraceableSession.ActivitySource.StartActivity(nameof(RecreateAsync))) + using (Activity activity = TraceableSession.ActivitySource.StartActivity()) { - return Task.FromResult((ISession)new TraceableSession(Session.Recreate(session, connection))); + return new TraceableSession(await Session.RecreateAsync(session, connection, ct).ConfigureAwait(false)); } } /// - public override Task RecreateAsync(ISession sessionTemplate, ITransportChannel channel, CancellationToken ct = default) + public override async Task RecreateAsync(ISession sessionTemplate, ITransportChannel channel, CancellationToken ct = default) { Session session = ValidateISession(sessionTemplate); - using (Activity activity = TraceableSession.ActivitySource.StartActivity(nameof(RecreateAsync))) + using (Activity activity = TraceableSession.ActivitySource.StartActivity()) { - return Task.FromResult((ISession)new TraceableSession(Session.Recreate(session, channel))); + return new TraceableSession(await Session.RecreateAsync(session, channel, ct).ConfigureAwait(false)); } } #endregion diff --git a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs index 6deed9b56..fb40e224e 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs @@ -682,9 +682,9 @@ public IApplicationConfigurationBuilderServerOptions SetMaxSubscriptionCount(int } /// - public IApplicationConfigurationBuilderServerOptions SetMaxEventQueueSize(int maxEventQueueSize) + public IApplicationConfigurationBuilderServerOptions SetMaxEventQueueSize(int setMaxEventQueueSize) { - ApplicationConfiguration.ServerConfiguration.MaxEventQueueSize = maxEventQueueSize; + ApplicationConfiguration.ServerConfiguration.MaxEventQueueSize = setMaxEventQueueSize; return this; } diff --git a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs index 56bc06e0a..185163d21 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs @@ -34,6 +34,7 @@ using System.Net; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Threading; using System.Threading.Tasks; using static Opc.Ua.Utils; @@ -49,13 +50,16 @@ public class ApplicationInstance /// Initializes a new instance of the class. /// public ApplicationInstance() - { } + { + DisableCertificateAutoCreation = false; + } /// /// Initializes a new instance of the class. /// /// The application configuration. public ApplicationInstance(ApplicationConfiguration applicationConfiguration) + : this() { m_applicationConfiguration = applicationConfiguration; } @@ -127,6 +131,19 @@ public ApplicationConfiguration ApplicationConfiguration /// Get or set the certificate password provider. /// public ICertificatePasswordProvider CertificatePasswordProvider { get; set; } + + /// + /// Get or set bool which indicates if the auto creation + /// of a new application certificate during startup is disabled. + /// Default is enabled./> + /// + /// + /// Prevents auto self signed cert creation in use cases + /// where an expired certificate should not be automatically + /// renewed or where it is required to only use certificates + /// provided by the user. + /// + public bool DisableCertificateAutoCreation { get; set; } #endregion #region Public Methods @@ -403,10 +420,10 @@ public Task CheckApplicationInstanceCertificate( /// /// Delete the application certificate. /// - public async Task DeleteApplicationInstanceCertificate() + public async Task DeleteApplicationInstanceCertificate(CancellationToken ct = default) { if (m_applicationConfiguration == null) throw new ArgumentException("Missing configuration."); - await DeleteApplicationInstanceCertificate(m_applicationConfiguration).ConfigureAwait(false); + await DeleteApplicationInstanceCertificateAsync(m_applicationConfiguration, ct).ConfigureAwait(false); } /// @@ -415,10 +432,12 @@ public async Task DeleteApplicationInstanceCertificate() /// if set to true no dialogs will be displayed. /// Minimum size of the key. /// The lifetime in months. + /// The cancellation token. public async Task CheckApplicationInstanceCertificate( bool silent, ushort minimumKeySize, - ushort lifeTimeInMonths) + ushort lifeTimeInMonths, + CancellationToken ct = default) { Utils.LogInfo("Checking application instance certificate."); @@ -449,7 +468,7 @@ public async Task CheckApplicationInstanceCertificate( if (certificate != null) { Utils.LogCertificate("Check certificate:", certificate); - bool certificateValid = await CheckApplicationInstanceCertificate(configuration, certificate, silent, minimumKeySize).ConfigureAwait(false); + bool certificateValid = await CheckApplicationInstanceCertificateAsync(configuration, certificate, silent, minimumKeySize, ct).ConfigureAwait(false); if (!certificateValid) { @@ -494,7 +513,7 @@ public async Task CheckApplicationInstanceCertificate( message.AppendLine("Use it instead?"); message.AppendLine("Requested: {0}"); message.AppendLine("Found: {1}"); - if (!await ApproveMessage(String.Format(message.ToString(), id.SubjectName, certificate.Subject), silent).ConfigureAwait(false)) + if (!await ApplicationInstance.ApproveMessageAsync(String.Format(message.ToString(), id.SubjectName, certificate.Subject), silent).ConfigureAwait(false)) { throw ServiceResultException.Create(StatusCodes.BadConfigurationError, message.ToString(), id.SubjectName, certificate.Subject); @@ -503,7 +522,7 @@ public async Task CheckApplicationInstanceCertificate( else { var message = new StringBuilder(); - message.AppendLine("Thumbprint was explicitly specified in the configuration. "); + message.AppendLine("Thumbprint was explicitly specified in the configuration."); message.AppendLine("Cannot generate a new certificate."); throw ServiceResultException.Create(StatusCodes.BadConfigurationError, message.ToString()); } @@ -512,8 +531,15 @@ public async Task CheckApplicationInstanceCertificate( if (certificate == null) { - certificate = await CreateApplicationInstanceCertificate(configuration, - minimumKeySize, lifeTimeInMonths).ConfigureAwait(false); + if (!DisableCertificateAutoCreation) + { + certificate = await CreateApplicationInstanceCertificateAsync(configuration, + minimumKeySize, lifeTimeInMonths, ct).ConfigureAwait(false); + } + else + { + Utils.LogWarning("Application Instance certificate auto creation is disabled."); + } if (certificate == null) { @@ -532,13 +558,24 @@ public async Task CheckApplicationInstanceCertificate( if (configuration.SecurityConfiguration.AddAppCertToTrustedStore) { // ensure it is trusted. - await AddToTrustedStore(configuration, certificate).ConfigureAwait(false); + await AddToTrustedStoreAsync(configuration, certificate, ct).ConfigureAwait(false); } } return true; } + /// + /// Adds a Certificate to the Trusted Store of the Application, needed e.g. for the GDS to trust it´s own CA + /// + /// The certificate to add to the store + /// The cancellation token + /// + public async Task AddOwnCertificateToTrustedStoreAsync(X509Certificate2 certificate, CancellationToken ct) + { + await AddToTrustedStoreAsync(m_applicationConfiguration, certificate, ct).ConfigureAwait(false); + } + /// /// Helper to suppress errors which are allowed for the application certificate validation. /// @@ -564,11 +601,12 @@ public void OnCertificateValidation(object sender, CertificateValidationEventArg /// /// Creates an application instance certificate if one does not already exist. /// - private async Task CheckApplicationInstanceCertificate( + private async Task CheckApplicationInstanceCertificateAsync( ApplicationConfiguration configuration, X509Certificate2 certificate, bool silent, - ushort minimumKeySize) + ushort minimumKeySize, + CancellationToken ct) { if (certificate == null) { @@ -592,13 +630,13 @@ private async Task CheckApplicationInstanceCertificate( { // validate certificate. configuration.CertificateValidator.CertificateValidation += certValidator.OnCertificateValidation; - configuration.CertificateValidator.Validate(certificate.HasPrivateKey ? new X509Certificate2(certificate.RawData) : certificate); + await configuration.CertificateValidator.ValidateAsync(certificate.HasPrivateKey ? new X509Certificate2(certificate.RawData) : certificate, ct).ConfigureAwait(false); } catch (Exception ex) { string message = Utils.Format( "Error validating certificate. Exception: {0}. Use certificate anyway?", ex.Message); - if (!await ApproveMessage(message, silent).ConfigureAwait(false)) + if (!await ApplicationInstance.ApproveMessageAsync(message, silent).ConfigureAwait(false)) { return false; } @@ -617,7 +655,7 @@ private async Task CheckApplicationInstanceCertificate( keySize, minimumKeySize); - if (!await ApproveMessage(message, silent).ConfigureAwait(false)) + if (!await ApplicationInstance.ApproveMessageAsync(message, silent).ConfigureAwait(false)) { return false; } @@ -626,7 +664,7 @@ private async Task CheckApplicationInstanceCertificate( // check domains. if (configuration.ApplicationType != ApplicationType.Client) { - if (!await CheckDomainsInCertificate(configuration, certificate, silent).ConfigureAwait(false)) + if (!await ApplicationInstance.CheckDomainsInCertificateAsync(configuration, certificate, silent, ct).ConfigureAwait(false)) { return false; } @@ -638,7 +676,7 @@ private async Task CheckApplicationInstanceCertificate( if (String.IsNullOrEmpty(applicationUri)) { string message = "The Application URI could not be read from the certificate. Use certificate anyway?"; - if (!await ApproveMessage(message, silent).ConfigureAwait(false)) + if (!await ApplicationInstance.ApproveMessageAsync(message, silent).ConfigureAwait(false)) { return false; } @@ -660,10 +698,11 @@ private async Task CheckApplicationInstanceCertificate( /// /// Checks that the domains in the server addresses match the domains in the certificates. /// - private async Task CheckDomainsInCertificate( + private static async Task CheckDomainsInCertificateAsync( ApplicationConfiguration configuration, X509Certificate2 certificate, - bool silent) + bool silent, + CancellationToken ct) { Utils.LogInfo("Check domains in certificate."); @@ -734,7 +773,7 @@ private async Task CheckDomainsInCertificate( valid = false; - if (await ApproveMessage(message, silent).ConfigureAwait(false)) + if (await ApplicationInstance.ApproveMessageAsync(message, silent).ConfigureAwait(false)) { valid = true; continue; @@ -752,15 +791,16 @@ private async Task CheckDomainsInCertificate( /// The configuration. /// Size of the key. /// The lifetime in months. + /// /// The new certificate - private static async Task CreateApplicationInstanceCertificate( + private static async Task CreateApplicationInstanceCertificateAsync( ApplicationConfiguration configuration, ushort keySize, - ushort lifeTimeInMonths - ) + ushort lifeTimeInMonths, + CancellationToken ct) { // delete any existing certificate. - await DeleteApplicationInstanceCertificate(configuration).ConfigureAwait(false); + await DeleteApplicationInstanceCertificateAsync(configuration, ct).ConfigureAwait(false); Utils.LogInfo("Creating application instance certificate."); @@ -792,16 +832,16 @@ ushort lifeTimeInMonths // need id for password provider id.Certificate = certificate; - certificate.AddToStore( + await certificate.AddToStoreAsync( id.StoreType, id.StorePath, - passwordProvider?.GetPassword(id) - ); + passwordProvider?.GetPassword(id), + ct).ConfigureAwait(false); // ensure the certificate is trusted. if (configuration.SecurityConfiguration.AddAppCertToTrustedStore) { - await AddToTrustedStore(configuration, certificate).ConfigureAwait(false); + await AddToTrustedStoreAsync(configuration, certificate, ct).ConfigureAwait(false); } // reload the certificate from disk. @@ -820,7 +860,8 @@ ushort lifeTimeInMonths /// Deletes an existing application instance certificate. /// /// The configuration instance that stores the configurable information for a UA application. - private static async Task DeleteApplicationInstanceCertificate(ApplicationConfiguration configuration) + /// + private static async Task DeleteApplicationInstanceCertificateAsync(ApplicationConfiguration configuration, CancellationToken ct) { // create a default certificate id none specified. CertificateIdentifier id = configuration.SecurityConfiguration.ApplicationCertificate; @@ -883,7 +924,8 @@ private static async Task DeleteApplicationInstanceCertificate(ApplicationConfig /// /// The application's configuration which specifies the location of the TrustedStore. /// The certificate to register. - private static async Task AddToTrustedStore(ApplicationConfiguration configuration, X509Certificate2 certificate) + /// The cancellation token. + private static async Task AddToTrustedStoreAsync(ApplicationConfiguration configuration, X509Certificate2 certificate, CancellationToken ct) { if (certificate == null) throw new ArgumentNullException(nameof(certificate)); @@ -966,7 +1008,7 @@ private static async Task AddToTrustedStore(ApplicationConfiguration configurati /// /// /// True if approved, false otherwise. - private async Task ApproveMessage(string message, bool silent) + private static async Task ApproveMessageAsync(string message, bool silent) { if (!silent && MessageDlg != null) { diff --git a/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs b/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs index a4d035150..8884d7d11 100644 --- a/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs +++ b/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs @@ -527,7 +527,7 @@ public interface IApplicationConfigurationBuilderTraceConfiguration : IApplicationConfigurationBuilderTraceConfiguration SetDeleteOnLoad(bool deleteOnLoad); /// - IApplicationConfigurationBuilderTraceConfiguration SetTraceMasks(int TraceMasks); + IApplicationConfigurationBuilderTraceConfiguration SetTraceMasks(int traceMasks); } /// diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index 5450f48a5..f3b3bb4bc 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -96,7 +96,7 @@ public ApplicationsNodeManager( m_database = database; m_request = request; m_certificateGroupFactory = certificateGroup; - m_certificateGroups = new Dictionary(); + m_certificateGroups = new Dictionary(); try { @@ -196,7 +196,7 @@ private NodeId GetTrustListId(NodeId certificateGroupId) certificateGroupId = m_defaultApplicationGroupId; } - CertificateGroup certificateGroup = null; + ICertificateGroup certificateGroup = null; if (m_certificateGroups.TryGetValue(certificateGroupId, out certificateGroup)) { return certificateGroup.DefaultTrustList?.NodeId; @@ -209,7 +209,7 @@ private NodeId GetTrustListId(NodeId certificateGroupId) NodeId certificateGroupId, NodeId certificateTypeId) { - CertificateGroup certificateGroup = null; + ICertificateGroup certificateGroup = null; if (m_certificateGroups.TryGetValue(certificateGroupId, out certificateGroup)) { if (!NodeId.IsNull(certificateTypeId)) @@ -291,7 +291,7 @@ private async Task RevokeCertificateAsync(byte[] certificate) } } - protected async Task InitializeCertificateGroup(CertificateGroupConfiguration certificateGroupConfiguration) + protected async Task InitializeCertificateGroup(CertificateGroupConfiguration certificateGroupConfiguration) { if (String.IsNullOrEmpty(certificateGroupConfiguration.SubjectName)) { @@ -303,7 +303,7 @@ protected async Task InitializeCertificateGroup(CertificateGro throw new ArgumentNullException("BaseStorePath not specified"); } - CertificateGroup certificateGroup = m_certificateGroupFactory.Create( + ICertificateGroup certificateGroup = m_certificateGroupFactory.Create( m_configuration.AuthoritiesStorePath, certificateGroupConfiguration); SetCertificateGroupNodes(certificateGroup); await certificateGroup.Init().ConfigureAwait(false); @@ -334,7 +334,7 @@ public override void CreateAddressSpace(IDictionary> e { try { - CertificateGroup certificateGroup = InitializeCertificateGroup(certificateGroupConfiguration).Result; + ICertificateGroup certificateGroup = InitializeCertificateGroup(certificateGroupConfiguration).Result; m_certificateGroups[certificateGroup.Id] = certificateGroup; } catch (Exception e) @@ -669,7 +669,7 @@ private string GetDefaultUserToken() return "USER"; } - private string GetSubjectName(ApplicationRecordDataType application, CertificateGroup certificateGroup, string subjectName) + private string GetSubjectName(ApplicationRecordDataType application, ICertificateGroup certificateGroup, string subjectName) { bool contextFound = false; @@ -774,7 +774,7 @@ private ServiceResult OnStartNewKeyPairRequest( certificateGroupId = ExpandedNodeId.ToNodeId(Opc.Ua.Gds.ObjectIds.Directory_CertificateGroups_DefaultApplicationGroup, Server.NamespaceUris); } - CertificateGroup certificateGroup = null; + ICertificateGroup certificateGroup = null; if (!m_certificateGroups.TryGetValue(certificateGroupId, out certificateGroup)) { return new ServiceResult(StatusCodes.BadInvalidArgument, "The certificateGroup is not supported."); @@ -893,7 +893,7 @@ private ServiceResult OnStartSigningRequest( certificateGroupId = ExpandedNodeId.ToNodeId(Opc.Ua.Gds.ObjectIds.Directory_CertificateGroups_DefaultApplicationGroup, Server.NamespaceUris); } - CertificateGroup certificateGroup = null; + ICertificateGroup certificateGroup = null; if (!m_certificateGroups.TryGetValue(certificateGroupId, out certificateGroup)) { return new ServiceResult(StatusCodes.BadInvalidArgument, "The CertificateGroupId does not refer to a supported certificateGroup."); @@ -985,7 +985,7 @@ private ServiceResult OnFinishRequest( return approvalState; } - CertificateGroup certificateGroup = null; + ICertificateGroup certificateGroup = null; if (!String.IsNullOrWhiteSpace(certificateGroupId)) { foreach (var group in m_certificateGroups) @@ -1382,7 +1382,7 @@ private ServiceResult VerifyApprovedState(CertificateRequestState state) private IApplicationsDatabase m_database; private ICertificateRequest m_request; private ICertificateGroup m_certificateGroupFactory; - private Dictionary m_certificateGroups; + private Dictionary m_certificateGroups; private Dictionary m_certTypeMap; #endregion } diff --git a/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs b/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs index 46560e416..1038b722e 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs @@ -113,7 +113,7 @@ public virtual async Task Init() } } - public virtual CertificateGroup Create( + public virtual ICertificateGroup Create( string storePath, CertificateGroupConfiguration certificateGroupConfiguration) { @@ -165,13 +165,18 @@ public virtual async Task NewKeyPairRequestAsync( } } - public virtual Task RevokeCertificateAsync( + public async virtual Task RevokeCertificateAsync( X509Certificate2 certificate) { - return RevokeCertificateAsync( + Task crl = RevokeCertificateAsync( AuthoritiesStorePath, certificate, null); + + //Also update TrustedList CRL so registerd Applications can get the new CRL + await crl.ContinueWith((_) => UpdateAuthorityCertInTrustedList(), TaskContinuationOptions.OnlyOnRanToCompletion).ConfigureAwait(false); + + return await crl.ConfigureAwait(false); } public virtual Task VerifySigningRequestAsync( @@ -206,7 +211,7 @@ public virtual Task VerifySigningRequestAsync( { if (ex is ServiceResultException) { - throw ex as ServiceResultException; + throw; } throw new ServiceResultException(StatusCodes.BadInvalidArgument, ex.Message); } @@ -275,7 +280,7 @@ public virtual async Task SigningRequestAsync( { if (ex is ServiceResultException) { - throw ex as ServiceResultException; + throw; } throw new ServiceResultException(StatusCodes.BadInvalidArgument, ex.Message); } @@ -286,6 +291,16 @@ public virtual async Task CreateCACertificateAsync( string subjectName ) { + // validate new subjectName matches the previous subject + // TODO: An issuer may modify the subject of the CA certificate, + // but then the configuration must be updated too! + // NOTE: not a strict requirement here for ASN.1 byte compare + if (!X509Utils.CompareDistinguishedName(subjectName, SubjectName)) + { + throw new ArgumentException("SubjectName provided does not match the SubjectName property of the CertificateGroup \n" + + "CA Certificate is not created until the subjectName " + SubjectName + " is provided", subjectName); + } + DateTime yesterday = DateTime.Today.AddDays(-1); X509Certificate2 newCertificate = CertificateFactory.CreateCertificate(subjectName) .SetNotBefore(yesterday) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs index 630f0310c..e73541b36 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs @@ -54,13 +54,17 @@ public GlobalDiscoverySampleServer( IApplicationsDatabase database, ICertificateRequest request, ICertificateGroup certificateGroup, - bool autoApprove = true + IUsersDatabase userDatabase, + bool autoApprove = true, + bool createStandardUsers = true ) { m_database = database; m_request = request; m_certificateGroup = certificateGroup; + m_userDatabase = userDatabase; m_autoApprove = autoApprove; + m_createStandardUsers = createStandardUsers; } #region Overridden Methods @@ -71,6 +75,12 @@ protected override void OnServerStarted(IServerInternal server) { base.OnServerStarted(server); + //ToDo delete this code in a production environment as this creates hardcoded passwords + if (m_createStandardUsers) + { + + RegisterDefaultUsers(); + } // request notifications when the user identity is changed. all valid users are accepted by default. server.SessionManager.ImpersonateUser += SessionManager_ImpersonateUser; } @@ -189,31 +199,30 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg { if (VerifyPassword(userNameToken)) { - switch (userNameToken.UserName) + if (userNameToken.UserName == "sysadmin") { // Server configuration administrator, manages the GDS server security - case "sysadmin": - { - args.Identity = new SystemConfigurationIdentity(new UserIdentity(userNameToken)); - Utils.LogInfo("SystemConfigurationAdmin Token Accepted: {0}", args.Identity.DisplayName); - return; - } - + args.Identity = new SystemConfigurationIdentity(new UserIdentity(userNameToken)); + Utils.LogInfo("SystemConfigurationAdmin Token Accepted: {0}", args.Identity.DisplayName); + return; + } + switch (m_userDatabase.GetUserRole(userNameToken.UserName)) + { // GDS administrator - case "appadmin": - { - args.Identity = new RoleBasedIdentity(new UserIdentity(userNameToken), GdsRole.ApplicationAdmin); - Utils.LogInfo("ApplicationAdmin Token Accepted: {0}", args.Identity.DisplayName); - return; - } + case GdsRole.ApplicationAdmin: + { + args.Identity = new RoleBasedIdentity(new UserIdentity(userNameToken), GdsRole.ApplicationAdmin); + Utils.LogInfo("ApplicationAdmin Token Accepted: {0}", args.Identity.DisplayName); + return; + } // GDS user - case "appuser": - { - args.Identity = new RoleBasedIdentity(new UserIdentity(userNameToken), GdsRole.ApplicationUser); - Utils.LogInfo("ApplicationUser Token Accepted: {0}", args.Identity.DisplayName); - return; - } + case GdsRole.ApplicationUser: + { + args.Identity = new RoleBasedIdentity(new UserIdentity(userNameToken), GdsRole.ApplicationUser); + Utils.LogInfo("ApplicationUser Token Accepted: {0}", args.Identity.DisplayName); + return; + } } } } @@ -279,8 +288,18 @@ private void VerifyUserTokenCertificate(X509Certificate2 certificate) private bool VerifyPassword(UserNameIdentityToken userNameToken) { - // TODO: check username/password permissions - return userNameToken.DecryptedPassword == "demo"; + return m_userDatabase.CheckCredentials(userNameToken.UserName, userNameToken.DecryptedPassword); + } + + /// + /// registers the default GDS users + /// ToDo delete this in a production environment + /// + private void RegisterDefaultUsers() + { + m_userDatabase.CreateUser("sysadmin", "demo", GdsRole.ApplicationAdmin); + m_userDatabase.CreateUser("appadmin", "demo", GdsRole.ApplicationAdmin); + m_userDatabase.CreateUser("appuser", "demo", GdsRole.ApplicationUser); } #endregion @@ -289,7 +308,9 @@ private bool VerifyPassword(UserNameIdentityToken userNameToken) private IApplicationsDatabase m_database = null; private ICertificateRequest m_request = null; private ICertificateGroup m_certificateGroup = null; + private IUsersDatabase m_userDatabase = null; private bool m_autoApprove; + private bool m_createStandardUsers; #endregion } } diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoveryServerConfiguration.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoveryServerConfiguration.cs index 4593834fc..9a8d4a4bc 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoveryServerConfiguration.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoveryServerConfiguration.cs @@ -86,6 +86,9 @@ private void Initialize() [DataMember(Order = 7)] public string DatabaseStorePath { get; set; } + + [DataMember(Order = 8)] + public string UsersDatabaseStorePath { get; set; } #endregion #region Private Members diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ICertificateGroup.cs b/Libraries/Opc.Ua.Gds.Server.Common/ICertificateGroup.cs index dbf0eda83..c0e00e2d8 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ICertificateGroup.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ICertificateGroup.cs @@ -63,7 +63,7 @@ public interface ICertificateGroup TrustListState DefaultTrustList { get; set; } bool UpdateRequired { get; set; } - CertificateGroup Create( + ICertificateGroup Create( string path, CertificateGroupConfiguration certificateGroupConfiguration); diff --git a/Libraries/Opc.Ua.Gds.Server.Common/IUsersDatabase.cs b/Libraries/Opc.Ua.Gds.Server.Common/IUsersDatabase.cs new file mode 100644 index 000000000..2d69b2e7c --- /dev/null +++ b/Libraries/Opc.Ua.Gds.Server.Common/IUsersDatabase.cs @@ -0,0 +1,81 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 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.Security; + +namespace Opc.Ua.Gds.Server.Database +{ + /// + /// An abstract interface to the user database which stores logins + /// + public interface IUsersDatabase + { + /// + /// Initialize User Database + /// + void Initialize(); + /// + /// Register new GDS User + /// + /// the username + /// the password + /// the GdsRole of the new User + /// true if registered sucessfull + bool CreateUser(string userName, string password, GdsRole role); + /// + /// Delete existring GDS user + /// + /// the user to delete + /// true if deleted sucessfully + bool DeleteUser(string userName); + /// + /// checks if the provided credentials fit a user + /// + /// the username + /// the password + /// true if userName + PW combination is correct + bool CheckCredentials(string userName, string password); + /// + /// returns the Role of the provided user + /// + /// + /// the GdsRole of the provided users + /// When the user is not found + GdsRole GetUserRole(string userName); + /// + /// changes the password of an existing users + /// + /// + /// + /// + /// true if change was sucessfull + bool ChangePassword(string userName, string oldPassword, string newPassword); + } +} diff --git a/Libraries/Opc.Ua.Gds.Server.Common/JsonUsersDatabase.cs b/Libraries/Opc.Ua.Gds.Server.Common/JsonUsersDatabase.cs new file mode 100644 index 000000000..d338aeb6f --- /dev/null +++ b/Libraries/Opc.Ua.Gds.Server.Common/JsonUsersDatabase.cs @@ -0,0 +1,99 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 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.IO; +using Newtonsoft.Json; + +namespace Opc.Ua.Gds.Server.Database.Linq +{ + /// + /// A GDS database with JSON storage. + /// + /// + /// This db is good for testing but not for production use. + /// + public class JsonUsersDatabase : LinQUsersDatabase + { + #region Constructors + /// + /// Create a JSON database. + /// + public JsonUsersDatabase(string fileName) + { + m_fileName = fileName; + } + + /// + /// Load the JSON application database. + /// + static public JsonUsersDatabase Load(string fileName) + { + if (fileName == null) throw new ArgumentNullException(nameof(fileName)); + try + { + if (File.Exists(fileName)) + { + string json = File.ReadAllText(fileName); + JsonUsersDatabase db = JsonConvert.DeserializeObject(json); + db.FileName = fileName; + return db; + } + } + catch + { + + } + return new JsonUsersDatabase(fileName); + } + #endregion + + #region Public Members + /// + /// Save the complete database. + /// + public override void Save() + { + string json = JsonConvert.SerializeObject(this, Formatting.Indented); + File.WriteAllText(m_fileName, json); + } + + /// + /// Get or set the filename. + /// + [JsonIgnore] + public string FileName { get { return m_fileName; } private set { m_fileName = value; } } + #endregion + + #region Private Fields + [JsonIgnore] + string m_fileName; + #endregion + } +} diff --git a/Libraries/Opc.Ua.Gds.Server.Common/LinqUsersDatabase.cs b/Libraries/Opc.Ua.Gds.Server.Common/LinqUsersDatabase.cs new file mode 100644 index 000000000..40bd40a98 --- /dev/null +++ b/Libraries/Opc.Ua.Gds.Server.Common/LinqUsersDatabase.cs @@ -0,0 +1,289 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 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.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Runtime.Serialization; +using System.Security.Cryptography; +using Newtonsoft.Json; +using static System.Net.Mime.MediaTypeNames; + +namespace Opc.Ua.Gds.Server.Database.Linq +{ + [Serializable] + class User + { + public Guid ID { get; set; } + public string UserName { get; set; } + public string Hash { get; set; } + public GdsRole GdsRole { get; set; } + } + + [Serializable] + public class LinQUsersDatabase : IUsersDatabase + { + #region IUsersDatabase + public virtual void Initialize() + { + } + + public bool CreateUser(string userName, string password, GdsRole role) + { + if (string.IsNullOrEmpty(userName)) + { + throw new ArgumentException("UserName cannot be empty.", nameof(userName)); + } + if (string.IsNullOrEmpty(password)) + { + throw new ArgumentException("Password cannot be empty.", nameof(password)); + } + if (//User Exists + Users.SingleOrDefault(x => x.UserName == userName) != null) + { + return false; + } + + string hash = Hash(password); + + var user = new User { UserName = userName, Hash = hash, GdsRole = role }; + + Users.Add(user); + + SaveChanges(); + + return true; + } + + public bool DeleteUser(string userName) + { + if (string.IsNullOrEmpty(userName)) + { + throw new ArgumentException("UserName cannot be empty.", nameof(userName)); + } + + var user = Users.SingleOrDefault(x => x.UserName == userName); + + if (user == null) + { + return false; + } + Users.Remove(user); + return true; + } + + public bool CheckCredentials(string userName, string password) + { + if (string.IsNullOrEmpty(userName)) + { + throw new ArgumentException("UserName cannot be empty.", nameof(userName)); + } + if (string.IsNullOrEmpty(password)) + { + throw new ArgumentException("Password cannot be empty.", nameof(password)); + } + + var user = Users.SingleOrDefault(x => x.UserName == userName); + + if (user == null) + { + return false; + } + + return Check(user.Hash, password); + } + + public GdsRole GetUserRole(string userName) + { + if (string.IsNullOrEmpty(userName)) + { + throw new ArgumentException("UserName cannot be empty.", nameof(userName)); + } + var user = Users.SingleOrDefault(x => x.UserName == userName); + + if (user == null) + { + throw new ArgumentException("No user found with the UserName " + userName); + } + + return user.GdsRole; + } + + public bool ChangePassword(string userName, string oldPassword, string newPassword) + { + if (string.IsNullOrEmpty(userName)) + { + throw new ArgumentException("UserName cannot be empty.", nameof(userName)); + } + if (string.IsNullOrEmpty(oldPassword)) + { + throw new ArgumentException("Current Password cannot be empty.", nameof(oldPassword)); + } + if (string.IsNullOrEmpty(newPassword)) + { + throw new ArgumentException("New Password cannot be empty.", nameof(newPassword)); + } + + var user = Users.SingleOrDefault(x => x.UserName == userName); + + if (user == null) + { + return false; + } + + if (Check(user.Hash, oldPassword)) + { + var newHash = Hash(newPassword); + user.Hash = newHash; + return true; + } + return false; + } + #endregion + + #region Public Members + public virtual void Save() + { + } + #endregion + + #region Private Members + private void SaveChanges() + { + lock (Lock) + { + queryCounterResetTime = DateTime.UtcNow; + // assign IDs to new users + var queryNewUsers = from x in Users + where x.ID == Guid.Empty + select x; + if (Users.Count > 0) + { + foreach (var user in queryNewUsers) + { + user.ID = Guid.NewGuid(); + } + } + Save(); + } + } + #endregion + + #region IPasswordHasher + private string Hash(string password) + { +#if NETSTANDARD2_0 + using (var algorithm = new Rfc2898DeriveBytes( + password, + kSaltSize, + kIterations)) + { +#else + using (var algorithm = new Rfc2898DeriveBytes( + password, + kSaltSize, + kIterations, + HashAlgorithmName.SHA512)) + { +#endif + var key = Convert.ToBase64String(algorithm.GetBytes(kKeySize)); + var salt = Convert.ToBase64String(algorithm.Salt); + + return $"{kIterations}.{salt}.{key}"; + } + } + + private bool Check(string hash, string password) + { + var separator = new Char[] { '.' }; + var parts = hash.Split(separator, 3); + + if (parts.Length != 3) + { + throw new FormatException("Unexpected hash format. " + + "Should be formatted as `{iterations}.{salt}.{hash}`"); + } + + var iterations = Convert.ToInt32(parts[0], CultureInfo.InvariantCulture.NumberFormat); + var salt = Convert.FromBase64String(parts[1]); + var key = Convert.FromBase64String(parts[2]); + +#if NETSTANDARD2_0 + using (var algorithm = new Rfc2898DeriveBytes( + password, + salt, + iterations)) + { +#else + using (var algorithm = new Rfc2898DeriveBytes( + password, + salt, + iterations, + HashAlgorithmName.SHA512)) + { +#endif + var keyToCheck = algorithm.GetBytes(kKeySize); + + var verified = keyToCheck.SequenceEqual(key); + + return verified; + } + } + +#endregion + + #region Internal Members + [OnDeserialized] + internal void OnDeserializedMethod(StreamingContext context) + { + Lock = new object(); + queryCounterResetTime = DateTime.UtcNow; + } + #endregion + + #region Internal Fields + [NonSerialized] + internal object Lock = new object(); + [NonSerialized] + internal DateTime queryCounterResetTime = DateTime.UtcNow; + [NonSerialized] + private const int kSaltSize = 16; // 128 bit + [NonSerialized] + private const int kIterations = 10000; // 10k + [NonSerialized] + private const int kKeySize = 32; // 256 bit + [JsonProperty] + internal ICollection Users = new HashSet(); + #endregion + } + + +} + diff --git a/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj b/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj index 8e457e0a1..2f375386b 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj +++ b/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj @@ -24,9 +24,12 @@ + + + + - diff --git a/Libraries/Opc.Ua.PubSub/Configuration/UaPubSubConfigurationHelper.cs b/Libraries/Opc.Ua.PubSub/Configuration/UaPubSubConfigurationHelper.cs index a4d71a564..097ce8dce 100644 --- a/Libraries/Opc.Ua.PubSub/Configuration/UaPubSubConfigurationHelper.cs +++ b/Libraries/Opc.Ua.PubSub/Configuration/UaPubSubConfigurationHelper.cs @@ -52,7 +52,7 @@ public static void SaveConfiguration(PubSubConfigurationDataType pubSubConfigura XmlWriterSettings settings = Utils.DefaultXmlWriterSettings(); settings.CloseOutput = true; - using (XmlWriter writer = XmlDictionaryWriter.Create(ostrm, settings)) + using (XmlWriter writer = XmlWriter.Create(ostrm, settings)) { DataContractSerializer serializer = new DataContractSerializer(typeof(PubSubConfigurationDataType)); serializer.WriteObject(writer, pubSubConfiguration); diff --git a/Libraries/Opc.Ua.PubSub/Configuration/UaPubSubConfigurator.cs b/Libraries/Opc.Ua.PubSub/Configuration/UaPubSubConfigurator.cs index 18123337c..64a068481 100644 --- a/Libraries/Opc.Ua.PubSub/Configuration/UaPubSubConfigurator.cs +++ b/Libraries/Opc.Ua.PubSub/Configuration/UaPubSubConfigurator.cs @@ -49,7 +49,7 @@ public class UaPubSubConfigurator /// internal static uint InvalidId = 0; - private object m_lock = new object(); + private readonly object m_lock = new object(); private PubSubConfigurationDataType m_pubSubConfiguration; private Dictionary m_idsToObjects; private Dictionary m_objectsToIds; @@ -195,9 +195,9 @@ public PublishedDataSetDataType FindPublishedDataSetByName(string name) /// public object FindObjectById(uint id) { - if (m_idsToObjects.ContainsKey(id)) + if (m_idsToObjects.TryGetValue(id, out object objectById)) { - return m_idsToObjects[id]; + return objectById; } return null; } @@ -209,9 +209,9 @@ public object FindObjectById(uint id) /// Returns if object was not found. public uint FindIdForObject(object configurationObject) { - if (m_objectsToIds.ContainsKey(configurationObject)) + if (m_objectsToIds.TryGetValue(configurationObject, out uint id)) { - return m_objectsToIds[configurationObject]; + return id; } return InvalidId; } @@ -224,9 +224,9 @@ public uint FindIdForObject(object configurationObject) public PubSubState FindStateForObject(object configurationObject) { uint id = FindIdForObject(configurationObject); - if (m_idsToPubSubState.ContainsKey(id)) + if (m_idsToPubSubState.TryGetValue(id, out PubSubState pubSubState)) { - return m_idsToPubSubState[id]; + return pubSubState; } return PubSubState.Error; } @@ -238,9 +238,9 @@ public PubSubState FindStateForObject(object configurationObject) /// Returns if the object. public PubSubState FindStateForId(uint id) { - if (m_idsToPubSubState.ContainsKey(id)) + if (m_idsToPubSubState.TryGetValue(id, out PubSubState pubsubState)) { - return m_idsToPubSubState[id]; + return pubsubState; } return PubSubState.Error; } @@ -252,9 +252,8 @@ public PubSubState FindStateForId(uint id) public object FindParentForObject(object configurationObject) { uint id = FindIdForObject(configurationObject); - if (id != InvalidId && m_idsToParentId.ContainsKey(id)) + if (id != InvalidId && m_idsToParentId.TryGetValue(id, out uint parentId)) { - uint parentId = m_idsToParentId[id]; return FindObjectById(parentId); } return null; @@ -439,8 +438,7 @@ public StatusCode RemovePublishedDataSet(uint publishedDataSetId) { lock (m_lock) { - PublishedDataSetDataType publishedDataSetDataType = FindObjectById(publishedDataSetId) as PublishedDataSetDataType; - if (publishedDataSetDataType == null) + if (!(FindObjectById(publishedDataSetId) is PublishedDataSetDataType publishedDataSetDataType)) { // Unexpected exception Utils.Trace(Utils.TraceMasks.Information, "Current configuration does not contain PublishedDataSetDataType with ConfigId = {0}", publishedDataSetId); @@ -523,8 +521,7 @@ public StatusCode AddExtensionField(uint publishedDataSetConfigId, KeyValuePair { lock (m_lock) { - PublishedDataSetDataType publishedDataSetDataType = FindObjectById(publishedDataSetConfigId) as PublishedDataSetDataType; - if (publishedDataSetDataType == null) + if (!(FindObjectById(publishedDataSetConfigId) is PublishedDataSetDataType publishedDataSetDataType)) { return StatusCodes.BadNodeIdInvalid; } @@ -577,9 +574,7 @@ public StatusCode RemoveExtensionField(uint publishedDataSetConfigId, uint exten { lock (m_lock) { - PublishedDataSetDataType publishedDataSetDataType = FindObjectById(publishedDataSetConfigId) as PublishedDataSetDataType; - KeyValuePair extensionFieldToRemove = FindObjectById(extensionFieldConfigId) as KeyValuePair; - if (publishedDataSetDataType == null || extensionFieldToRemove == null) + if (!(FindObjectById(publishedDataSetConfigId) is PublishedDataSetDataType publishedDataSetDataType) || !(FindObjectById(extensionFieldConfigId) is KeyValuePair extensionFieldToRemove)) { return StatusCodes.BadNodeIdInvalid; } @@ -714,8 +709,7 @@ public StatusCode RemoveConnection(uint connectionId) { lock (m_lock) { - PubSubConnectionDataType pubSubConnectionDataType = FindObjectById(connectionId) as PubSubConnectionDataType; - if (pubSubConnectionDataType == null) + if (!(FindObjectById(connectionId) is PubSubConnectionDataType pubSubConnectionDataType)) { // Unexpected exception Utils.Trace(Utils.TraceMasks.Information, "Current configuration does not contain PubSubConnectionDataType with ConfigId = {0}", connectionId); @@ -812,8 +806,7 @@ public StatusCode AddWriterGroup(uint parentConnectionId, WriterGroupDataType wr // remember collections DataSetWriterDataTypeCollection dataSetWriters = new DataSetWriterDataTypeCollection(writerGroupDataType.DataSetWriters); writerGroupDataType.DataSetWriters.Clear(); - PubSubConnectionDataType parentConnection = m_idsToObjects[parentConnectionId] as PubSubConnectionDataType; - if (parentConnection != null) + if (m_idsToObjects[parentConnectionId] is PubSubConnectionDataType parentConnection) { //validate duplicate name bool duplicateName = false; @@ -886,8 +879,7 @@ public StatusCode RemoveWriterGroup(uint writerGroupId) { lock (m_lock) { - WriterGroupDataType writerGroupDataType = FindObjectById(writerGroupId) as WriterGroupDataType; - if (writerGroupDataType == null) + if (!(FindObjectById(writerGroupId) is WriterGroupDataType writerGroupDataType)) { // Unexpected exception Utils.Trace(Utils.TraceMasks.Information, "Current configuration does not contain WriterGroupDataType with ConfigId = {0}", writerGroupId); @@ -984,8 +976,7 @@ public StatusCode AddDataSetWriter(uint parentWriterGroupId, DataSetWriterDataTy { lock (m_lock) { - WriterGroupDataType parentWriterGroup = m_idsToObjects[parentWriterGroupId] as WriterGroupDataType; - if (parentWriterGroup != null) + if (m_idsToObjects[parentWriterGroupId] is WriterGroupDataType parentWriterGroup) { //validate duplicate name bool duplicateName = false; @@ -1047,8 +1038,7 @@ public StatusCode RemoveDataSetWriter(uint dataSetWriterId) { lock (m_lock) { - DataSetWriterDataType dataSetWriterDataType = FindObjectById(dataSetWriterId) as DataSetWriterDataType; - if (dataSetWriterDataType == null) + if (!(FindObjectById(dataSetWriterId) is DataSetWriterDataType dataSetWriterDataType)) { // Unexpected exception Utils.Trace(Utils.TraceMasks.Information, "Current configuration does not contain DataSetWriterDataType with ConfigId = {0}", dataSetWriterId); @@ -1141,8 +1131,7 @@ public StatusCode AddReaderGroup(uint parentConnectionId, ReaderGroupDataType re // remember collections DataSetReaderDataTypeCollection dataSetReaders = new DataSetReaderDataTypeCollection(readerGroupDataType.DataSetReaders); readerGroupDataType.DataSetReaders.Clear(); - PubSubConnectionDataType parentConnection = m_idsToObjects[parentConnectionId] as PubSubConnectionDataType; - if (parentConnection != null) + if (m_idsToObjects[parentConnectionId] is PubSubConnectionDataType parentConnection) { //validate duplicate name bool duplicateName = false; @@ -1217,8 +1206,7 @@ public StatusCode RemoveReaderGroup(uint readerGroupId) { lock (m_lock) { - ReaderGroupDataType readerGroupDataType = FindObjectById(readerGroupId) as ReaderGroupDataType; - if (readerGroupDataType == null) + if (!(FindObjectById(readerGroupId) is ReaderGroupDataType readerGroupDataType)) { Utils.Trace(Utils.TraceMasks.Information, "Current configuration does not contain ReaderGroupDataType with ConfigId = {0}", readerGroupId); return StatusCodes.BadInvalidArgument; @@ -1314,8 +1302,7 @@ public StatusCode AddDataSetReader(uint parentReaderGroupId, DataSetReaderDataTy { lock (m_lock) { - ReaderGroupDataType parentReaderGroup = m_idsToObjects[parentReaderGroupId] as ReaderGroupDataType; - if (parentReaderGroup != null) + if (m_idsToObjects[parentReaderGroupId] is ReaderGroupDataType parentReaderGroup) { //validate duplicate name bool duplicateName = false; @@ -1377,8 +1364,7 @@ public StatusCode RemoveDataSetReader(uint dataSetReaderId) { lock (m_lock) { - DataSetReaderDataType dataSetReaderDataType = FindObjectById(dataSetReaderId) as DataSetReaderDataType; - if (dataSetReaderDataType == null) + if (!(FindObjectById(dataSetReaderId) is DataSetReaderDataType dataSetReaderDataType)) { // Unexpected exception Utils.Trace(Utils.TraceMasks.Information, "Current configuration does not contain DataSetReaderDataType with ConfigId = {0}", dataSetReaderId); @@ -1636,7 +1622,7 @@ private void UpdateChildrenState(object configurationObject) /// Configured Enabled flag. /// of the parent configured object. /// - private PubSubState GetInitialPubSubState(bool enabled, PubSubState parentPubSubState) + private static PubSubState GetInitialPubSubState(bool enabled, PubSubState parentPubSubState) { if (enabled) { diff --git a/Libraries/Opc.Ua.PubSub/Configuration/WriterGroupEventArgs.cs b/Libraries/Opc.Ua.PubSub/Configuration/WriterGroupEventArgs.cs index 7b58a4c9f..c656e2870 100644 --- a/Libraries/Opc.Ua.PubSub/Configuration/WriterGroupEventArgs.cs +++ b/Libraries/Opc.Ua.PubSub/Configuration/WriterGroupEventArgs.cs @@ -50,5 +50,5 @@ public class WriterGroupEventArgs : EventArgs /// Reference to object /// public WriterGroupDataType WriterGroupDataType { get; set; } - } + } } diff --git a/Libraries/Opc.Ua.PubSub/DataSetDecodeErrorEventArgs.cs b/Libraries/Opc.Ua.PubSub/DataSetDecodeErrorEventArgs.cs index 6241edf4d..c59d7ab53 100644 --- a/Libraries/Opc.Ua.PubSub/DataSetDecodeErrorEventArgs.cs +++ b/Libraries/Opc.Ua.PubSub/DataSetDecodeErrorEventArgs.cs @@ -28,7 +28,6 @@ * ======================================================================*/ using System; -using Opc.Ua.PubSub.Encoding; namespace Opc.Ua.PubSub { diff --git a/Libraries/Opc.Ua.PubSub/DataSetWriterConfigurationResponse.cs b/Libraries/Opc.Ua.PubSub/DataSetWriterConfigurationResponse.cs new file mode 100644 index 000000000..53f401da0 --- /dev/null +++ b/Libraries/Opc.Ua.PubSub/DataSetWriterConfigurationResponse.cs @@ -0,0 +1,54 @@ +/* ======================================================================== + * Copyright (c) 2005-2021 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/ + * ======================================================================*/ + +namespace Opc.Ua.PubSub +{ + /// + /// Data Set Writer Configuration message + /// + public class DataSetWriterConfigurationResponse + { + /// + /// DataSetWriterIds contained in the configuration information. + /// + public ushort[] DataSetWriterIds { get; set; } + + /// + /// The field shall contain only the entry for the requested or changed DataSetWriters in the WriterGroup. + /// + public WriterGroupDataType DataSetWriterConfig { get; set; } + + /// + /// Status codes indicating the capability of the Publisher to provide + /// configuration information for the DataSetWriterIds.The size of the array + /// shall match the size of the DataSetWriterIds array. + /// + public StatusCode[] StatusCodes { get; set; } + } +} diff --git a/Libraries/Opc.Ua.PubSub/DatasetWriterConfigurationEventArgs.cs b/Libraries/Opc.Ua.PubSub/DatasetWriterConfigurationEventArgs.cs new file mode 100644 index 000000000..112463565 --- /dev/null +++ b/Libraries/Opc.Ua.PubSub/DatasetWriterConfigurationEventArgs.cs @@ -0,0 +1,64 @@ +/* ======================================================================== + * Copyright (c) 2005-2021 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; + +namespace Opc.Ua.PubSub +{ + /// + /// Class that contains data related to DatasetWriterConfigurationReceived event + /// + public class DataSetWriterConfigurationEventArgs : EventArgs + { + /// + /// Get the ids of the DataSetWriters + /// + public ushort[] DataSetWriterIds { get; internal set; } + + /// + /// Get the received configuration. + /// + public WriterGroupDataType DataSetWriterConfiguration { get; internal set; } + + /// + /// Get the source information + /// + public string Source { get; internal set; } + + /// + /// Get the publisher Id + /// + public object PublisherId { get; internal set; } + + /// + /// Get the statuses code of the DataSetWriter + /// + public StatusCode[] StatusCodes { get; internal set; } + } +} diff --git a/Libraries/Opc.Ua.PubSub/Encoding/JsonDataSetMessage.cs b/Libraries/Opc.Ua.PubSub/Encoding/JsonDataSetMessage.cs index 3c19d35a9..49ef2c053 100644 --- a/Libraries/Opc.Ua.PubSub/Encoding/JsonDataSetMessage.cs +++ b/Libraries/Opc.Ua.PubSub/Encoding/JsonDataSetMessage.cs @@ -203,9 +203,8 @@ private void DecodePossibleDataSetReader(IJsonDecoder jsonDecoder, DataSetReader payloadStructureName = null; } - Dictionary payload = token as Dictionary; - if (payload != null && dataSetReader.DataSetMetaData != null) + if (token is Dictionary payload && dataSetReader.DataSetMetaData != null) { DecodeErrorReason = ValidateMetadataVersion(dataSetReader.DataSetMetaData.ConfigurationVersion); @@ -248,10 +247,6 @@ private void DecodePossibleDataSetReader(IJsonDecoder jsonDecoder, DataSetReader /// private DataSet DecodePayloadContent(IJsonDecoder jsonDecoder, DataSetReaderDataType dataSetReader) { - TargetVariablesDataType targetVariablesData = - ExtensionObject.ToEncodeable(dataSetReader.SubscribedDataSet) - as TargetVariablesDataType; - DataSetMetaDataType dataSetMetaData = dataSetReader.DataSetMetaData; object token; @@ -370,7 +365,7 @@ private DataSet DecodePayloadContent(IJsonDecoder jsonDecoder, DataSetReaderData dataField.FieldMetaData = dataSetMetaData?.Fields[i]; dataField.Value = dataValues[i]; - if (targetVariablesData != null && targetVariablesData.TargetVariables != null + if (ExtensionObject.ToEncodeable(dataSetReader.SubscribedDataSet) is TargetVariablesDataType targetVariablesData && targetVariablesData.TargetVariables != null && i < targetVariablesData.TargetVariables.Count) { // remember the target Attribute and target nodeId @@ -533,7 +528,7 @@ private void EncodeField(IJsonEncoder encoder, Field field) /// Decode RawData type /// /// - private object DecodeRawData(IJsonDecoder jsonDecoder, FieldMetaData fieldMetaData, string fieldName) + private static object DecodeRawData(IJsonDecoder jsonDecoder, FieldMetaData fieldMetaData, string fieldName) { if (fieldMetaData.BuiltInType != 0) { @@ -611,7 +606,7 @@ private void DecodeDataSetMessageHeader(IJsonDecoder jsonDecoder) /// /// Decode a scalar type /// - private object DecodeRawScalar(IJsonDecoder jsonDecoder, byte builtInType, string fieldName) + private static object DecodeRawScalar(IJsonDecoder jsonDecoder, byte builtInType, string fieldName) { try { diff --git a/Libraries/Opc.Ua.PubSub/Encoding/JsonNetworkMessage.cs b/Libraries/Opc.Ua.PubSub/Encoding/JsonNetworkMessage.cs index b4f6af81a..b650c9613 100644 --- a/Libraries/Opc.Ua.PubSub/Encoding/JsonNetworkMessage.cs +++ b/Libraries/Opc.Ua.PubSub/Encoding/JsonNetworkMessage.cs @@ -30,6 +30,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; namespace Opc.Ua.PubSub.Encoding { @@ -166,7 +167,7 @@ public void SetNetworkMessageContentMask(JsonNetworkMessageContentMask networkMe { NetworkMessageContentMask = networkMessageContentMask; - foreach (JsonDataSetMessage jsonDataSetMessage in DataSetMessages) + foreach (JsonDataSetMessage jsonDataSetMessage in DataSetMessages.Cast()) { jsonDataSetMessage.HasDataSetMessageHeader = HasDataSetMessageHeader; } @@ -215,9 +216,8 @@ public override void Encode(IServiceMessageContext messageContext, Stream stream if (HasSingleDataSetMessage) { // encode single dataset message - JsonDataSetMessage jsonDataSetMessage = DataSetMessages[0] as JsonDataSetMessage; - if (jsonDataSetMessage != null) + if (DataSetMessages[0] is JsonDataSetMessage jsonDataSetMessage) { if (!jsonDataSetMessage.HasDataSetMessageHeader) { @@ -240,8 +240,7 @@ public override void Encode(IServiceMessageContext messageContext, Stream stream // the NetworkMessage is the contents of the Messages field (e.g. a JSON array of DataSetMessages). foreach (var message in DataSetMessages) { - JsonDataSetMessage jsonDataSetMessage = message as JsonDataSetMessage; - if (jsonDataSetMessage != null) + if (message is JsonDataSetMessage jsonDataSetMessage) { jsonDataSetMessage.Encode(encoder); } @@ -259,7 +258,7 @@ public override void Encode(IServiceMessageContext messageContext, Stream stream /// public override void Decode(IServiceMessageContext context, byte[] message, IList dataSetReaders) { - string json = System.Text.Encoding.UTF8.GetString(message); + string json = System.Text.Encoding.UTF8.GetString(message); using (IJsonDecoder jsonDecoder = new JsonDecoder(json, context)) { @@ -278,7 +277,7 @@ public override void Decode(IServiceMessageContext context, byte[] message, ILis } } - + #endregion #region Private Methods - Encoding @@ -342,7 +341,7 @@ private void EncodeNetworkMessageHeader(IJsonEncoder jsonEncoder) } } } - + /// /// Encode DataSetMessages /// @@ -353,8 +352,7 @@ private void EncodeMessages(IJsonEncoder encoder) if (HasSingleDataSetMessage) { // encode single dataset message - JsonDataSetMessage jsonDataSetMessage = DataSetMessages[0] as JsonDataSetMessage; - if (jsonDataSetMessage != null) + if (DataSetMessages[0] is JsonDataSetMessage jsonDataSetMessage) { jsonDataSetMessage.Encode(encoder, kFieldMessages); } @@ -364,8 +362,7 @@ private void EncodeMessages(IJsonEncoder encoder) encoder.PushArray(kFieldMessages); foreach (var message in DataSetMessages) { - JsonDataSetMessage jsonDataSetMessage = message as JsonDataSetMessage; - if (jsonDataSetMessage != null) + if (message is JsonDataSetMessage jsonDataSetMessage) { jsonDataSetMessage.Encode(encoder); } @@ -441,7 +438,7 @@ private void DecodeNetworkMessageHeader(IJsonDecoder jsonDecoder) NetworkMessageContentMask |= JsonNetworkMessageContentMask.DataSetClassId; } - if (m_jsonNetworkMessageType == JSONNetworkMessageType.DataSetMetaData) + if (m_jsonNetworkMessageType == JSONNetworkMessageType.DataSetMetaData) { // for metadata messages the DataSetWriterId field is mandatory if (jsonDecoder.ReadField(nameof(DataSetWriterId), out token)) @@ -559,9 +556,7 @@ private void DecodeSubscribedDataSets(IJsonDecoder jsonDecoder, IList public void DecodePossibleDataSetReader(BinaryDecoder binaryDecoder, DataSetReaderDataType dataSetReader) { - UadpDataSetReaderMessageDataType messageSettings = ExtensionObject.ToEncodeable(dataSetReader.MessageSettings) - as UadpDataSetReaderMessageDataType; - if (messageSettings != null) + if (ExtensionObject.ToEncodeable(dataSetReader.MessageSettings) is UadpDataSetReaderMessageDataType messageSettings) { //StartPositionInStream is calculated but different from reader configuration dataset cannot be decoded if (StartPositionInStream != messageSettings.DataSetOffset) @@ -382,7 +380,7 @@ private void EncodeMessageDataKeyFrame(BinaryEncoder binaryEncoder) // DataSetFieldCount is not persisted for RawData foreach (Field field in DataSet.Fields) { - EncodeFieldAsRawData(binaryEncoder, field); + UadpDataSetMessage.EncodeFieldAsRawData(binaryEncoder, field); } break; case FieldTypeEncodingMask.Reserved: @@ -405,7 +403,7 @@ private void EncodeMessageDataDeltaFrame(BinaryEncoder binaryEncoder) FieldTypeEncodingMask fieldType = (FieldTypeEncodingMask)(((byte)DataSetFlags1 & kFieldTypeUsedBits) >> 1); - for (int i =0; i < DataSet.Fields.Length; i++) + for (int i = 0; i < DataSet.Fields.Length; i++) { Field field = DataSet.Fields[i]; if (field == null) continue; // ignore null fields @@ -424,13 +422,13 @@ private void EncodeMessageDataDeltaFrame(BinaryEncoder binaryEncoder) binaryEncoder.WriteDataValue("FieldValue", field.Value); break; case FieldTypeEncodingMask.RawData: - EncodeFieldAsRawData(binaryEncoder, field); + UadpDataSetMessage.EncodeFieldAsRawData(binaryEncoder, field); break; case FieldTypeEncodingMask.Reserved: // ignore break; } - } + } } #endregion @@ -520,9 +518,7 @@ private DataSet DecodeMessageDataKeyFrame(BinaryDecoder binaryDecoder, DataSetRe fieldCount = binaryDecoder.ReadUInt16("DataSetFieldCount"); } - TargetVariablesDataType targetVariablesData = - ExtensionObject.ToEncodeable(dataSetReader.SubscribedDataSet) as TargetVariablesDataType; - + // check configuration version List dataValues = new List(); switch (fieldType) @@ -562,12 +558,12 @@ private DataSet DecodeMessageDataKeyFrame(BinaryDecoder binaryDecoder, DataSetRe List dataFields = new List(); for (int i = 0; i < dataValues.Count; i++) - { + { Field dataField = new Field(); dataField.FieldMetaData = dataSetMetaData?.Fields[i]; dataField.Value = dataValues[i]; - if (targetVariablesData != null && targetVariablesData.TargetVariables != null + if (ExtensionObject.ToEncodeable(dataSetReader.SubscribedDataSet) is TargetVariablesDataType targetVariablesData && targetVariablesData.TargetVariables != null && i < targetVariablesData.TargetVariables.Count) { // remember the target Attribute and target nodeId @@ -611,17 +607,14 @@ private DataSet DecodeMessageDataDeltaFrame(BinaryDecoder binaryDecoder, DataSet if (dataSetMetaData != null) { - TargetVariablesDataType targetVariablesData = - ExtensionObject.ToEncodeable(dataSetReader.SubscribedDataSet) as TargetVariablesDataType; - // create dataFields collection List dataFields = new List(); for (int i = 0; i < dataSetMetaData.Fields.Count; i++) { Field dataField = new Field(); dataField.FieldMetaData = dataSetMetaData?.Fields[i]; - - if (targetVariablesData != null && targetVariablesData.TargetVariables != null + + if (ExtensionObject.ToEncodeable(dataSetReader.SubscribedDataSet) is TargetVariablesDataType targetVariablesData && targetVariablesData.TargetVariables != null && i < targetVariablesData.TargetVariables.Count) { // remember the target Attribute and target nodeId @@ -634,7 +627,7 @@ private DataSet DecodeMessageDataDeltaFrame(BinaryDecoder binaryDecoder, DataSet // read number of fields encoded in this delta frame message ushort fieldCount = fieldCount = binaryDecoder.ReadUInt16("FieldCount"); - for(int i =0; i < fieldCount; i++) + for (int i = 0; i < fieldCount; i++) { ushort fieldIndex = binaryDecoder.ReadUInt16("FieldIndex"); // update value in dataFields @@ -653,7 +646,7 @@ private DataSet DecodeMessageDataDeltaFrame(BinaryDecoder binaryDecoder, DataSet { var decodedValue = DecodeRawData(binaryDecoder, fieldMetaData); dataFields[fieldIndex].Value = new DataValue(new Variant(decodedValue)); - } + } break; case FieldTypeEncodingMask.Reserved: // ignore @@ -668,21 +661,21 @@ private DataSet DecodeMessageDataDeltaFrame(BinaryDecoder binaryDecoder, DataSet dataSet.DataSetWriterId = DataSetWriterId; dataSet.SequenceNumber = SequenceNumber; return dataSet; - } + } } catch (Exception ex) { - Utils.Trace(ex, "UadpDataSetMessage.DecodeMessageDataDeltaFrame"); + Utils.Trace(ex, "UadpDataSetMessage.DecodeMessageDataDeltaFrame"); } return null; } - + /// /// Encodes field value as RawData /// /// /// - private void EncodeFieldAsRawData(BinaryEncoder binaryEncoder, Field field) + private static void EncodeFieldAsRawData(BinaryEncoder binaryEncoder, Field field) { try { @@ -797,7 +790,7 @@ private object DecodeRawData(BinaryDecoder binaryDecoder, FieldMetaData fieldMet { case ValueRanks.Scalar: - return DecodeRawScalar(binaryDecoder, fieldMetaData.BuiltInType); + return UadpDataSetMessage.DecodeRawScalar(binaryDecoder, fieldMetaData.BuiltInType); case ValueRanks.OneDimension: case ValueRanks.TwoDimensions: @@ -828,7 +821,7 @@ private object DecodeRawData(BinaryDecoder binaryDecoder, FieldMetaData fieldMet /// /// /// The decoded object - private object DecodeRawScalar(BinaryDecoder binaryDecoder, byte builtInType) + private static object DecodeRawScalar(BinaryDecoder binaryDecoder, byte builtInType) { switch ((BuiltInType)builtInType) { diff --git a/Libraries/Opc.Ua.PubSub/Encoding/UadpNetworkMessage.cs b/Libraries/Opc.Ua.PubSub/Encoding/UadpNetworkMessage.cs index ff8345070..3e0f74503 100644 --- a/Libraries/Opc.Ua.PubSub/Encoding/UadpNetworkMessage.cs +++ b/Libraries/Opc.Ua.PubSub/Encoding/UadpNetworkMessage.cs @@ -50,7 +50,10 @@ public class UadpNetworkMessage : UaNetworkMessage private object m_publisherId; private UADPNetworkMessageType m_uadpNetworkMessageType; private UADPNetworkMessageDiscoveryType m_discoveryType; - private UInt16[] m_dataSetWriterIds; + private ushort[] m_dataSetWriterIds; + + private WriterGroupDataType m_dataSetWriterConfiguration; + private StatusCode[] m_statusCodes; #endregion #region Constructor @@ -90,7 +93,7 @@ public UadpNetworkMessage(WriterGroupDataType writerGroupConfiguration, DataSetM m_uadpNetworkMessageType = UADPNetworkMessageType.DiscoveryResponse; m_discoveryType = UADPNetworkMessageDiscoveryType.DataSetMetaData; - SetFlagsDiscoveryResponseMetaData(); + SetFlagsDiscoveryResponse(); } /// @@ -109,6 +112,47 @@ public UadpNetworkMessage(UADPNetworkMessageDiscoveryType discoveryType) SetFlagsDiscoveryRequest(); } + /// + /// Create new instance of as a DiscoveryResponse of PublisherEndpoints type + /// + /// + /// + public UadpNetworkMessage(EndpointDescription[] publisherEndpoints, StatusCode publisherProvidesEndpoints) + : base(null, new List()) + { + UADPVersion = kUadpVersion; + DataSetClassId = Guid.Empty; + Timestamp = DateTime.UtcNow; + + PublisherEndpoints = publisherEndpoints; + PublisherProvideEndpoints = publisherProvidesEndpoints; + + m_uadpNetworkMessageType = UADPNetworkMessageType.DiscoveryResponse; + m_discoveryType = UADPNetworkMessageDiscoveryType.PublisherEndpoint; + + SetFlagsDiscoveryResponse(); + } + + /// + /// Create new instance of as a DiscoveryResponse of DataSetWriterConfiguration message + /// + public UadpNetworkMessage(ushort[] writerIds, WriterGroupDataType writerConfig, StatusCode[] streamStatusCodes) + : base(null, new List()) + { + UADPVersion = kUadpVersion; + DataSetClassId = Guid.Empty; + Timestamp = DateTime.UtcNow; + + DataSetWriterIds = writerIds; + + m_uadpNetworkMessageType = UADPNetworkMessageType.DiscoveryResponse; + m_discoveryType = UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration; + m_dataSetWriterConfiguration = writerConfig; + m_statusCodes = streamStatusCodes; + + SetFlagsDiscoveryResponse(); + } + #endregion #region Properties @@ -134,11 +178,41 @@ public UADPNetworkMessageType UADPNetworkMessageType /// public UADPNetworkMessageDiscoveryType UADPDiscoveryType { - get { return m_discoveryType;} + get { return m_discoveryType; } + } + + /// + /// Get/Set the StatusCodes + /// + public StatusCode[] MessageStatusCodes + { + get + { + return m_statusCodes; + } + set + { + m_statusCodes = value; + } + } + + /// + /// Get the DataSetWriterConfig + /// + public WriterGroupDataType DataSetWriterConfiguration + { + get + { + return m_dataSetWriterConfiguration; + } + set + { + m_dataSetWriterConfiguration = value; + } } /// - /// Get/Set the DataSetWriterIds + /// Discovery DataSetWriter Identifiers /// public UInt16[] DataSetWriterIds { @@ -336,6 +410,20 @@ public object PublisherId #endregion + #region Publisher endpoints + /// + /// Discovery Publisher Endpoints message + /// + internal EndpointDescription[] PublisherEndpoints { get; set; } + + + + /// + /// StatusCode that specifies if a Discovery message provides PublisherEndpoints + /// + internal StatusCode PublisherProvideEndpoints { get; set; } + #endregion + #endregion #region Public Methods @@ -371,38 +459,23 @@ public override byte[] Encode(IServiceMessageContext messageContext) /// The stream to use. public override void Encode(IServiceMessageContext messageContext, Stream stream) { - using (BinaryEncoder encoder = new BinaryEncoder(stream, messageContext, true)) + using (BinaryEncoder binaryEncoder = new BinaryEncoder(stream, messageContext, true)) { if (m_uadpNetworkMessageType == UADPNetworkMessageType.DataSetMessage) { - EncodeDataSetNetworkMessageType(encoder); + EncodeDataSetNetworkMessageType(binaryEncoder); } else { - EncodeNetworkMessageHeader(encoder); + EncodeNetworkMessageHeader(binaryEncoder); if (m_uadpNetworkMessageType == UADPNetworkMessageType.DiscoveryResponse) { - encoder.WriteByte("ResponseType", (byte)m_discoveryType); - // A strictly monotonically increasing sequence number assigned to each discovery response sent in the scope of a PublisherId. - encoder.WriteUInt16("SequenceNumber", SequenceNumber); - - switch (m_discoveryType) - { - case UADPNetworkMessageDiscoveryType.DataSetMetaData: - EncodeDataSetMetaData(encoder); - break; - case UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration: - case UADPNetworkMessageDiscoveryType.PublisherEndpoint: - // not implemented - break; - } + EncodeDiscoveryResponse(binaryEncoder); } else if (m_uadpNetworkMessageType == UADPNetworkMessageType.DiscoveryRequest) { - encoder.WriteByte("RequestType", (byte)m_discoveryType); - // write DataSetWriterIds - encoder.WriteUInt16Array("DataSetWriterIds", m_dataSetWriterIds); + EncodeDiscoveryRequest(binaryEncoder); } } } @@ -433,39 +506,24 @@ public override void Decode(IServiceMessageContext context, byte[] message, ILis } else if (m_uadpNetworkMessageType == UADPNetworkMessageType.DiscoveryResponse) { - // Decode the Discovery Response Header - m_discoveryType = (UADPNetworkMessageDiscoveryType)binaryDecoder.ReadByte("ResponseType"); - // A strictly monotonically increasing sequence number assigned to each discovery response sent in the scope of a PublisherId. - SequenceNumber = binaryDecoder.ReadUInt16("SequenceNumber"); - - switch (m_discoveryType) - { - case UADPNetworkMessageDiscoveryType.DataSetMetaData: - DecodeMetaDataMessage(binaryDecoder); - break; - case UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration: - case UADPNetworkMessageDiscoveryType.PublisherEndpoint: - // not implemented - break; - } + DecodeDiscoveryResponse(binaryDecoder); } else if (m_uadpNetworkMessageType == UADPNetworkMessageType.DiscoveryRequest) { - // Decode the Discovery Response Header - m_discoveryType = (UADPNetworkMessageDiscoveryType)binaryDecoder.ReadByte("ResponseType"); - m_dataSetWriterIds = binaryDecoder.ReadUInt16Array("DataSetWriterIds")?.ToArray(); + DecodeDiscoveryRequest(binaryDecoder); } } } - #endregion - #region Private Methods - Encoding - /// - /// Encodes the DataSet Network message in a binary stream. - /// - /// - private void EncodeDataSetNetworkMessageType(BinaryEncoder binaryEncoder) - { + #endregion + + #region Private Methods - Encoding + /// + /// Encodes the DataSet Network message in a binary stream. + /// + /// + private void EncodeDataSetNetworkMessageType(BinaryEncoder binaryEncoder) + { if (binaryEncoder == null) { throw new ArgumentException(nameof(binaryEncoder)); @@ -492,20 +550,58 @@ private void EncodeDataSetMetaData(BinaryEncoder binaryEncoder) } else { - Utils.Trace("The UADP DiscoveryResponse DataSetMetaData message cannot be encoded: The DataSetWriterId property is missing. Value 0 will be used."); + Trace("The UADP DiscoveryResponse DataSetMetaData message cannot be encoded: The DataSetWriterId property is missing. Value 0 will be used."); binaryEncoder.WriteUInt16("DataSetWriterId", 0); } if (m_metadata == null) { - Utils.Trace("The UADP DiscoveryResponse DataSetMetaData message cannot be encoded: The MetaData property is missing. Value null will be used."); + Trace("The UADP DiscoveryResponse DataSetMetaData message cannot be encoded: The MetaData property is missing. Value null will be used."); } binaryEncoder.WriteEncodeable("MetaData", m_metadata, typeof(DataSetMetaDataType)); - // temporary write StatusCode.Good binaryEncoder.WriteStatusCode("StatusCode", StatusCodes.Good); } + /// + /// Encodes the NetworkMessage as a DiscoveryResponse of DataSetWriterConfiguration Type + /// + /// + private void EncodeDataSetWriterConfiguration(BinaryEncoder binaryEncoder) + { + if (DataSetWriterIds != null) + { + binaryEncoder.WriteUInt16Array("DataSetWriterId", DataSetWriterIds); + } + else + { + Trace("The UADP DiscoveryResponse DataSetWriterConfiguration message cannot be encoded: The DataSetWriterId property is missing. Value 0 will be used."); + binaryEncoder.WriteUInt16Array("DataSetWriterIds", new List()); + } + + if (DataSetWriterIds == null) + { + Trace("The UADP DiscoveryResponse DataSetWriterConfiguration message cannot be encoded: The DataSetWriterConfiguration property is missing. Value null will be used."); + } + else + { + binaryEncoder.WriteEncodeable("DataSetWriterConfiguration", DataSetWriterConfiguration, typeof(WriterGroupDataType)); + } + + binaryEncoder.WriteStatusCodeArray("StatusCodes", MessageStatusCodes); + } + + /// + /// Encodes the NetworkMessage as a DiscoveryResponse of EndpointDescription[] Type + /// + /// + private void EncodePublisherEndpoints(BinaryEncoder binaryEncoder) + { + binaryEncoder.WriteEncodeableArray("Endpoints", PublisherEndpoints, typeof(EndpointDescription)); + + binaryEncoder.WriteStatusCode("statusCode", PublisherProvideEndpoints); + } + /// /// Set All flags before encode/decode for a NetworkMessage that contains DataSet messages /// @@ -639,11 +735,10 @@ private void SetFlagsDataSetNetworkMessageType() #endregion } - /// - /// Set All flags before encode/decode for a NetworkMessage that contains A DiscoveryResponse containing data set metadata + /// Set All flags before encode/decode for a NetworkMessage that contains a DiscoveryResponse containing data set metadata /// - private void SetFlagsDiscoveryResponseMetaData() + private void SetFlagsDiscoveryResponse() { /* DiscoveryResponse: * UADPFlags bits 5 and 6 shall be false, bits 4 and 7 shall be true @@ -674,9 +769,6 @@ private void SetFlagsDiscoveryRequest() ExtendedFlags2 = ExtendedFlags2EncodingMask.NetworkMessageWithDiscoveryRequest; } - - - /// /// Decode the stream from decoder parameter and produce a Dataset /// @@ -803,12 +895,12 @@ If the value is 0 (null), the parameter shall be ignored and all received DataSe m_uaDataSetMessages.Clear(); m_uaDataSetMessages.AddRange(dataSetMessages); } - + } catch (Exception ex) { // Unexpected exception in DecodeSubscribedDataSets - Utils.Trace(ex, "UadpNetworkMessage.DecodeSubscribedDataSets"); + Trace(ex, "UadpNetworkMessage.DecodeSubscribedDataSets"); } } @@ -823,10 +915,42 @@ private void DecodeMetaDataMessage(BinaryDecoder binaryDecoder) // temporary write StatusCode.Good StatusCode statusCode = binaryDecoder.ReadStatusCode("StatusCode"); - Utils.Trace("DecodeMetaDataMessage returned: ", statusCode); + Trace("DecodeMetaDataMessage returned: ", statusCode); } + /// + /// Decode the binaryDecoder content as Endpoints message + /// + /// + private void DecodePublisherEndpoints(BinaryDecoder binaryDecoder) + { + PublisherEndpoints = (EndpointDescription[])binaryDecoder.ReadEncodeableArray("Endpoints", typeof(EndpointDescription)); + + PublisherProvideEndpoints = binaryDecoder.ReadStatusCode("statusCode"); + + Trace("DecodePublisherEndpointsMessage returned: ", PublisherProvideEndpoints); + } + + /// + /// Decode the binaryDecoder content as a DataSetWriterConfiguration message + /// + /// the decoder + private void DecodeDataSetWriterConfigurationMessage(BinaryDecoder binaryDecoder) + { + DataSetWriterIds = binaryDecoder.ReadUInt16Array("DataSetWriterIds").ToArray(); + + var dataSetWriterConfigurationDecoded = binaryDecoder.ReadEncodeable("DataSetWriterConfiguration", typeof(WriterGroupDataType)) as WriterGroupDataType; + + DataSetWriterConfiguration = dataSetWriterConfigurationDecoded.MaxNetworkMessageSize != 0 + ? dataSetWriterConfigurationDecoded + : null; + + // temporary write StatusCode.Good + MessageStatusCodes = binaryDecoder.ReadStatusCodeArray("StatusCodes").ToArray(); + Trace("DecodeDataSetWriterConfigurationMessage returned: ", MessageStatusCodes); + } + /// /// Encode Network Message Header /// @@ -851,7 +975,7 @@ private void EncodeNetworkMessageHeader(BinaryEncoder encoder) { if (PublisherId == null) { - Utils.Trace(TraceMasks.Error, "NetworkMessageHeader cannot be encoded. PublisherId is null but it is expected to be encoded."); + Trace(TraceMasks.Error, "NetworkMessageHeader cannot be encoded. PublisherId is null but it is expected to be encoded."); } else { @@ -1036,15 +1160,32 @@ private void EncodeSecurityFooter(BinaryEncoder encoder) } } - /// - /// Encode signature - /// - /// - private void EncodeSignature(BinaryEncoder encoder) + private void EncodeDiscoveryResponse(BinaryEncoder binaryEncoder) { - // encoder.WriteByteArray("Signature", Signature); + binaryEncoder.WriteByte("ResponseType", (byte)m_discoveryType); + // A strictly monotonically increasing sequence number assigned to each discovery response sent in the scope of a PublisherId. + binaryEncoder.WriteUInt16("SequenceNumber", SequenceNumber); + + switch (m_discoveryType) + { + case UADPNetworkMessageDiscoveryType.DataSetMetaData: + EncodeDataSetMetaData(binaryEncoder); + break; + case UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration: + EncodeDataSetWriterConfiguration(binaryEncoder); + break; + case UADPNetworkMessageDiscoveryType.PublisherEndpoint: + EncodePublisherEndpoints(binaryEncoder); + break; + } } + private void EncodeDiscoveryRequest(BinaryEncoder binaryEncoder) + { + // RequestType => InformationType + binaryEncoder.WriteByte("RequestType", (byte)m_discoveryType); + binaryEncoder.WriteUInt16Array("DataSetWriterIds", DataSetWriterIds); + } #endregion #region Private Methods - Decoding @@ -1265,24 +1406,37 @@ private void DecodeSecurityHeader(BinaryDecoder decoder) } /// - /// Decode security footer + /// Decode the Discovery Request Header /// - /// - private void DecodeSecurityFooter(BinaryDecoder decoder) + /// + private void DecodeDiscoveryRequest(BinaryDecoder binaryDecoder) { - if ((SecurityFlags & SecurityFlagsEncodingMask.SecurityFooter) != 0) - { - SecurityFooter = decoder.ReadByteArray("SecurityFooter").ToArray(); - } + m_discoveryType = (UADPNetworkMessageDiscoveryType)binaryDecoder.ReadByte("RequestType"); + DataSetWriterIds = binaryDecoder.ReadUInt16Array("DataSetWriterIds")?.ToArray(); } /// - /// Decode signature + /// Decode the Discovery Response Header /// - /// - private void DecodeSignature(BinaryDecoder decoder) + /// + private void DecodeDiscoveryResponse(BinaryDecoder binaryDecoder) { - // Signature = decoder.ReadByteArray("Signature").ToArray(); + m_discoveryType = (UADPNetworkMessageDiscoveryType)binaryDecoder.ReadByte("ResponseType"); + // A strictly monotonically increasing sequence number assigned to each discovery response sent in the scope of a PublisherId. + SequenceNumber = binaryDecoder.ReadUInt16("SequenceNumber"); + + switch (m_discoveryType) + { + case UADPNetworkMessageDiscoveryType.DataSetMetaData: + DecodeMetaDataMessage(binaryDecoder); + break; + case UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration: + DecodeDataSetWriterConfigurationMessage(binaryDecoder); + break; + case UADPNetworkMessageDiscoveryType.PublisherEndpoint: + DecodePublisherEndpoints(binaryDecoder); + break; + } } #endregion } diff --git a/Libraries/Opc.Ua.PubSub/Enums.cs b/Libraries/Opc.Ua.PubSub/Enums.cs index 90201960d..54b93307c 100644 --- a/Libraries/Opc.Ua.PubSub/Enums.cs +++ b/Libraries/Opc.Ua.PubSub/Enums.cs @@ -154,11 +154,11 @@ public enum UADPNetworkMessageType /// /// DataSet message /// - DataSetMessage = 0, + DataSetMessage = 0, /// /// Discovery Request message /// - DiscoveryRequest = 4, + DiscoveryRequest = 4, /// /// Discovery Response message /// diff --git a/Libraries/Opc.Ua.PubSub/IUaPubSubConnection.cs b/Libraries/Opc.Ua.PubSub/IUaPubSubConnection.cs index 08f54df00..da5efd92d 100644 --- a/Libraries/Opc.Ua.PubSub/IUaPubSubConnection.cs +++ b/Libraries/Opc.Ua.PubSub/IUaPubSubConnection.cs @@ -91,5 +91,5 @@ public interface IUaPubSubConnection : IDisposable /// Get current list of dataset readers available in this UaSubscriber component /// List GetOperationalDataSetReaders(); - } + } } diff --git a/Libraries/Opc.Ua.PubSub/IntervalRunner.cs b/Libraries/Opc.Ua.PubSub/IntervalRunner.cs index 2a7e2b4fa..fe8dbd9be 100644 --- a/Libraries/Opc.Ua.PubSub/IntervalRunner.cs +++ b/Libraries/Opc.Ua.PubSub/IntervalRunner.cs @@ -101,7 +101,7 @@ public double Interval /// public void Start() { - Task.Run(Process).ConfigureAwait(false); + Task.Run(ProcessAsync).ConfigureAwait(false); Utils.Trace("IntervalRunner with id: {0} was started.", Id); } @@ -152,7 +152,7 @@ protected virtual void Dispose(bool disposing) /// /// Periodically executes the . /// - private async Task Process() + private async Task ProcessAsync() { do { diff --git a/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj b/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj index a9f12be74..34632e558 100644 --- a/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj +++ b/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj @@ -1,4 +1,4 @@ - + Opc.Ua.PubSub @@ -30,13 +30,13 @@ - + - + diff --git a/Libraries/Opc.Ua.PubSub/PublishedData/DataCollector.cs b/Libraries/Opc.Ua.PubSub/PublishedData/DataCollector.cs index abe06c182..1535254e3 100644 --- a/Libraries/Opc.Ua.PubSub/PublishedData/DataCollector.cs +++ b/Libraries/Opc.Ua.PubSub/PublishedData/DataCollector.cs @@ -71,8 +71,7 @@ public bool ValidatePublishedDataSet(PublishedDataSetDataType publishedDataSet) Utils.Trace(Utils.TraceMasks.Error, "The DataSetMetaData field is null."); return false; } - PublishedDataItemsDataType publishedDataItems = ExtensionObject.ToEncodeable(publishedDataSet.DataSetSource) as PublishedDataItemsDataType; - if (publishedDataItems != null && publishedDataItems.PublishedData != null) + if (ExtensionObject.ToEncodeable(publishedDataSet.DataSetSource) is PublishedDataItemsDataType publishedDataItems && publishedDataItems.PublishedData != null) { if (publishedDataItems.PublishedData.Count != publishedDataSet.DataSetMetaData.Fields.Count) { @@ -115,10 +114,7 @@ public void RemovePublishedDataSet(PublishedDataSetDataType publishedDataSet) { throw new ArgumentException(nameof(publishedDataSet)); } - if (m_publishedDataSetsByName.ContainsKey(publishedDataSet.Name)) - { - m_publishedDataSetsByName.Remove(publishedDataSet.Name); - } + m_publishedDataSetsByName.Remove(publishedDataSet.Name); } /// @@ -139,9 +135,8 @@ public DataSet CollectData(string dataSetName) DataSet dataSet = new DataSet(dataSetName); dataSet.DataSetMetaData = publishedDataSet.DataSetMetaData; - PublishedDataItemsDataType publishedDataItems = ExtensionObject.ToEncodeable(publishedDataSet.DataSetSource) as PublishedDataItemsDataType; - if (publishedDataItems != null && publishedDataItems.PublishedData != null && publishedDataItems.PublishedData.Count > 0) + if (ExtensionObject.ToEncodeable(publishedDataSet.DataSetSource) is PublishedDataItemsDataType publishedDataItems && publishedDataItems.PublishedData != null && publishedDataItems.PublishedData.Count > 0) { dataSet.Fields = new Field[publishedDataItems.PublishedData.Count]; for (int i = 0; i < publishedDataItems.PublishedData.Count; i++) @@ -217,8 +212,7 @@ bool shouldBringToConstraints(uint givenStrlen) case BuiltInType.String: if (field.FieldMetaData.ValueRank == ValueRanks.Scalar) { - string strFieldValue = variant.Value as string; - if (strFieldValue != null && shouldBringToConstraints((uint)strFieldValue.Length)) + if (variant.Value is string strFieldValue && shouldBringToConstraints((uint)strFieldValue.Length)) { variant.Value = strFieldValue.Substring(0, (int)field.FieldMetaData.MaxStringLength); dataValue.Value = variant; @@ -243,8 +237,7 @@ bool shouldBringToConstraints(uint givenStrlen) case BuiltInType.ByteString: if (field.FieldMetaData.ValueRank == ValueRanks.Scalar) { - byte[] byteStringFieldValue = variant.Value as byte[]; - if (byteStringFieldValue != null && shouldBringToConstraints((uint)byteStringFieldValue.Length)) + if (variant.Value is byte[] byteStringFieldValue && shouldBringToConstraints((uint)byteStringFieldValue.Length)) { byte[] byteArray = (byte[])byteStringFieldValue.Clone(); Array.Resize(ref byteArray, (int)field.FieldMetaData.MaxStringLength); @@ -303,9 +296,9 @@ public PublishedDataSetDataType GetPublishedDataSet(string dataSetName) throw new ArgumentException(nameof(dataSetName)); } - if (m_publishedDataSetsByName.ContainsKey(dataSetName)) + if (m_publishedDataSetsByName.TryGetValue(dataSetName, out PublishedDataSetDataType value)) { - return m_publishedDataSetsByName[dataSetName]; + return value; } return null; } diff --git a/Libraries/Opc.Ua.PubSub/PublisherEndpointsEventArgs.cs b/Libraries/Opc.Ua.PubSub/PublisherEndpointsEventArgs.cs new file mode 100644 index 000000000..6d5bf8b7b --- /dev/null +++ b/Libraries/Opc.Ua.PubSub/PublisherEndpointsEventArgs.cs @@ -0,0 +1,59 @@ +/* ======================================================================== + * Copyright (c) 2005-2021 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; + +namespace Opc.Ua.PubSub +{ + /// + /// Class that contains data related to PublisherEndpoints event + /// + public class PublisherEndpointsEventArgs : EventArgs + { + /// + /// Get the received Publisher identifier. + /// + public object PublisherId { get; internal set; } + + /// + /// Get the source information + /// + public string Source { get; internal set; } + + /// + /// Get the received Publisher Endpoints. + /// + public EndpointDescription[] PublisherEndpoints { get; internal set; } + + /// + /// Get the status code of the DataSetWriter + /// + public StatusCode StatusCode { get; internal set; } + } +} diff --git a/Libraries/Opc.Ua.PubSub/SubscribedDataEventArgs.cs b/Libraries/Opc.Ua.PubSub/SubscribedDataEventArgs.cs index 05ce24182..f9e93a6ca 100644 --- a/Libraries/Opc.Ua.PubSub/SubscribedDataEventArgs.cs +++ b/Libraries/Opc.Ua.PubSub/SubscribedDataEventArgs.cs @@ -39,7 +39,7 @@ public class SubscribedDataEventArgs : EventArgs /// /// Get the received NetworkMessage. /// - public UaNetworkMessage NetworkMessage { get; internal set; } + public UaNetworkMessage NetworkMessage { get; internal set; } /// /// Get the source information diff --git a/Libraries/Opc.Ua.PubSub/Transport/IUadpDiscoveryMessages.cs b/Libraries/Opc.Ua.PubSub/Transport/IUadpDiscoveryMessages.cs new file mode 100644 index 000000000..cd6dedd1e --- /dev/null +++ b/Libraries/Opc.Ua.PubSub/Transport/IUadpDiscoveryMessages.cs @@ -0,0 +1,103 @@ +/* ======================================================================== + * Copyright (c) 2005-2021 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.Collections.Generic; + +namespace Opc.Ua.PubSub +{ + /// + /// UADP Discovery messages interface + /// + public interface IUadpDiscoveryMessages + { + /// + /// Set GetPublisherEndpoints callback used by the subscriber to receive PublisherEndpoints data from publisher + /// + /// + void GetPublisherEndpointsCallback(GetPublisherEndpointsEventHandler eventHandler); + + /// + /// Set GetDataSetWriterIds callback used by the subscriber to receive DataSetWriter ids from publisher + /// + /// + void GetDataSetWriterConfigurationCallback(GetDataSetWriterIdsEventHandler eventHandler); + + /// + /// Create and return the list of EndpointDescription to be used only by UADP Discovery response messages + /// + /// + /// + /// + /// + UaNetworkMessage CreatePublisherEndpointsNetworkMessage(EndpointDescription[] endpoints, + StatusCode publisherProvideEndpointsStatusCode, object publisherId); + + /// + /// Create and return the list of DataSetMetaData response messages + /// + /// + /// + IList CreateDataSetMetaDataNetworkMessages(UInt16[] dataSetWriterIds); + + /// + /// Create and return the list of DataSetWriterConfiguration response message + /// + /// DatasetWriter ids + /// + IList CreateDataSetWriterCofigurationMessage(UInt16[] dataSetWriterIds); + + /// + /// Request UADP Discovery DataSetWriterConfiguration messages + /// + void RequestDataSetWriterConfiguration(); + + /// + /// Request UADP Discovery DataSetMetaData messages + /// + void RequestDataSetMetaData(); + + /// + /// Request UADP Discovery Publisher endpoints only + /// + void RequestPublisherEndpoints(); + } + + /// + /// Get PublisherEndpoints event handler + /// + /// + public delegate IList GetPublisherEndpointsEventHandler(); + + /// + /// Get DataSetWriterConfiguration ids event handler + /// + /// + public delegate IList GetDataSetWriterIdsEventHandler(UaPubSubApplication uaPubSubApplication); +} diff --git a/Libraries/Opc.Ua.PubSub/Transport/MqttClientCreator.cs b/Libraries/Opc.Ua.PubSub/Transport/MqttClientCreator.cs index 64a13db41..6567d6794 100644 --- a/Libraries/Opc.Ua.PubSub/Transport/MqttClientCreator.cs +++ b/Libraries/Opc.Ua.PubSub/Transport/MqttClientCreator.cs @@ -28,7 +28,6 @@ * ======================================================================*/ using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MQTTnet; @@ -39,7 +38,7 @@ namespace Opc.Ua.PubSub.Transport internal static class MqttClientCreator { #region Private - private static readonly Lazy mqttClientFactory = new Lazy(() => new MqttFactory()); + private static readonly Lazy s_mqttClientFactory = new Lazy(() => new MqttFactory()); #endregion /// @@ -55,7 +54,7 @@ internal static async Task GetMqttClientAsync(int reconnectInterval Func receiveMessageHandler, StringCollection topicFilter = null) { - IMqttClient mqttClient = mqttClientFactory.Value.CreateMqttClient(); + IMqttClient mqttClient = s_mqttClientFactory.Value.CreateMqttClient(); // Hook the receiveMessageHandler in case we deal with a subscriber if ((receiveMessageHandler != null) && (topicFilter != null)) @@ -102,7 +101,7 @@ internal static async Task GetMqttClientAsync(int reconnectInterval mqttClient?.Options?.ClientId, e.Reason, e.ClientWasConnected); - await Connect(reconnectInterval, mqttClientOptions, mqttClient).ConfigureAwait(false); + await ConnectAsync(reconnectInterval, mqttClientOptions, mqttClient).ConfigureAwait(false); } catch (Exception excOnDisconnect) { @@ -110,7 +109,7 @@ internal static async Task GetMqttClientAsync(int reconnectInterval } }; - await Connect(reconnectInterval, mqttClientOptions, mqttClient).ConfigureAwait(false); + await ConnectAsync(reconnectInterval, mqttClientOptions, mqttClient).ConfigureAwait(false); return mqttClient; } @@ -121,11 +120,11 @@ internal static async Task GetMqttClientAsync(int reconnectInterval /// /// /// - private static async Task Connect(int reconnectInterval, MqttClientOptions mqttClientOptions, IMqttClient mqttClient) + private static async Task ConnectAsync(int reconnectInterval, MqttClientOptions mqttClientOptions, IMqttClient mqttClient) { try { - var result = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None).ConfigureAwait(false); + MqttClientConnectResult result = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None).ConfigureAwait(false); if (MqttClientConnectResultCode.Success == result.ResultCode) { Utils.Trace("MQTT client {0} successfully connected", mqttClient?.Options?.ClientId); diff --git a/Libraries/Opc.Ua.PubSub/Transport/MqttClientProtocolConfiguration.cs b/Libraries/Opc.Ua.PubSub/Transport/MqttClientProtocolConfiguration.cs index 987b28401..d31d5be8c 100644 --- a/Libraries/Opc.Ua.PubSub/Transport/MqttClientProtocolConfiguration.cs +++ b/Libraries/Opc.Ua.PubSub/Transport/MqttClientProtocolConfiguration.cs @@ -34,7 +34,7 @@ using System.Security.Cryptography.X509Certificates; namespace Opc.Ua.PubSub.Transport -{ +{ /// /// The certificates used by the tls/ssl layer /// @@ -45,12 +45,6 @@ public class MqttTlsCertificates private X509Certificate m_caCertificate; private X509Certificate m_clientCertificate; - private string m_caCertificatePath; - private string m_clientCertificatePath; - private string m_clientCertificatePassword; - - KeyValuePairCollection m_keyValuePairs; - #endregion Private menbers #region Constructor @@ -63,29 +57,29 @@ public class MqttTlsCertificates public MqttTlsCertificates(string caCertificatePath = null, string clientCertificatePath = null, string clientCertificatePassword = null) { - m_caCertificatePath = caCertificatePath ?? ""; - m_clientCertificatePath = clientCertificatePath ?? ""; - m_clientCertificatePassword = clientCertificatePassword ?? ""; + CaCertificatePath = caCertificatePath ?? ""; + ClientCertificatePath = clientCertificatePath ?? ""; + ClientCertificatePassword = clientCertificatePassword ?? ""; - if (!string.IsNullOrEmpty(m_caCertificatePath)) + if (!string.IsNullOrEmpty(CaCertificatePath)) { - m_caCertificate = X509Certificate.CreateFromCertFile(m_caCertificatePath); + m_caCertificate = X509Certificate.CreateFromCertFile(CaCertificatePath); } if (!string.IsNullOrEmpty(clientCertificatePath)) { - m_clientCertificate = new X509Certificate2(clientCertificatePath, m_clientCertificatePassword); + m_clientCertificate = new X509Certificate2(clientCertificatePath, ClientCertificatePassword); } - m_keyValuePairs = new KeyValuePairCollection(); + KeyValuePairs = new KeyValuePairCollection(); QualifiedName qCaCertificatePath = EnumMqttClientConfigurationParameters.TlsCertificateCaCertificatePath.ToString(); - m_keyValuePairs.Add(new KeyValuePair { Key = qCaCertificatePath, Value = m_caCertificatePath }); + KeyValuePairs.Add(new KeyValuePair { Key = qCaCertificatePath, Value = CaCertificatePath }); QualifiedName qClientCertificatePath = EnumMqttClientConfigurationParameters.TlsCertificateClientCertificatePath.ToString(); - m_keyValuePairs.Add(new KeyValuePair { Key = qClientCertificatePath, Value = m_clientCertificatePath }); + KeyValuePairs.Add(new KeyValuePair { Key = qClientCertificatePath, Value = ClientCertificatePath }); QualifiedName qClientCertificatePassword = EnumMqttClientConfigurationParameters.TlsCertificateClientCertificatePassword.ToString(); - m_keyValuePairs.Add(new KeyValuePair { Key = qClientCertificatePassword, Value = m_clientCertificatePassword }); + KeyValuePairs.Add(new KeyValuePair { Key = qClientCertificatePassword, Value = ClientCertificatePassword }); } /// @@ -94,44 +88,44 @@ public MqttTlsCertificates(string caCertificatePath = null, /// public MqttTlsCertificates(KeyValuePairCollection keyValuePairs) { - m_caCertificatePath = ""; + CaCertificatePath = ""; QualifiedName qCaCertificatePath = EnumMqttClientConfigurationParameters.TlsCertificateCaCertificatePath.ToString(); - m_caCertificatePath = keyValuePairs.Find(kvp => kvp.Key.Name.Equals(qCaCertificatePath.Name))?.Value.Value as string; + CaCertificatePath = keyValuePairs.Find(kvp => kvp.Key.Name.Equals(qCaCertificatePath.Name))?.Value.Value as string; - m_clientCertificatePath = ""; + ClientCertificatePath = ""; QualifiedName qClientCertificatePath = EnumMqttClientConfigurationParameters.TlsCertificateClientCertificatePath.ToString(); - m_clientCertificatePath = keyValuePairs.Find(kvp => kvp.Key.Name.Equals(qClientCertificatePath.Name))?.Value.Value as string; + ClientCertificatePath = keyValuePairs.Find(kvp => kvp.Key.Name.Equals(qClientCertificatePath.Name))?.Value.Value as string; - m_clientCertificatePassword = ""; + ClientCertificatePassword = ""; QualifiedName qClientCertificatePassword = EnumMqttClientConfigurationParameters.TlsCertificateClientCertificatePassword.ToString(); - m_clientCertificatePassword = keyValuePairs.Find(kvp => kvp.Key.Name.Equals(qClientCertificatePassword.Name))?.Value.Value as string; + ClientCertificatePassword = keyValuePairs.Find(kvp => kvp.Key.Name.Equals(qClientCertificatePassword.Name))?.Value.Value as string; - m_keyValuePairs = keyValuePairs; + KeyValuePairs = keyValuePairs; - if (!string.IsNullOrEmpty(m_caCertificatePath)) + if (!string.IsNullOrEmpty(CaCertificatePath)) { - m_caCertificate = X509Certificate.CreateFromCertFile(m_caCertificatePath); + m_caCertificate = X509Certificate.CreateFromCertFile(CaCertificatePath); } - if (!string.IsNullOrEmpty(clientCertificatePath)) + if (!string.IsNullOrEmpty(ClientCertificatePath)) { - m_clientCertificate = new X509Certificate2(clientCertificatePath, m_clientCertificatePassword); + m_clientCertificate = new X509Certificate2(ClientCertificatePath, ClientCertificatePassword); } } #endregion Constructor #region Internal Properties - internal string caCertificatePath { get { return m_caCertificatePath; } set { m_caCertificatePath = value; } } - internal string clientCertificatePath { get { return m_clientCertificatePath; } set { m_clientCertificatePath = value; } } - internal string clientCertificatePassword { get { return m_clientCertificatePassword; } set { m_clientCertificatePassword = value; } } + internal string CaCertificatePath { get; set; } + internal string ClientCertificatePath { get; set; } + internal string ClientCertificatePassword { get; set; } - internal KeyValuePairCollection KeyValuePairs { get { return m_keyValuePairs; } set { m_keyValuePairs = value; } } + internal KeyValuePairCollection KeyValuePairs { get; set; } internal List X509Certificates { get { - List values = new List(); + var values = new List(); if (m_caCertificate != null) { values.Add(m_caCertificate); @@ -152,35 +146,21 @@ internal List X509Certificates /// public class MqttTlsOptions { - #region Private - MqttTlsCertificates m_certificates; - SslProtocols m_SslProtocolVersion; - bool m_allowUntrustedCertificates; - bool m_ignoreCertificateChainErrors; - bool m_ignoreRevocationListErrors; - - CertificateStoreIdentifier m_trustedIssuerCertificates; - CertificateStoreIdentifier m_trustedPeerCertificates; - CertificateStoreIdentifier m_rejectedCertificateStore; - - KeyValuePairCollection m_keyValuePairs; - #endregion - #region Constructor /// /// Default constructor /// public MqttTlsOptions() { - m_certificates = null; - m_SslProtocolVersion = SslProtocols.None; - m_allowUntrustedCertificates = false; - m_ignoreCertificateChainErrors = false; - m_ignoreRevocationListErrors = false; - - m_trustedIssuerCertificates = null; - m_trustedPeerCertificates = null; - m_rejectedCertificateStore = null; + Certificates = null; + SslProtocolVersion = SslProtocols.None; + AllowUntrustedCertificates = false; + IgnoreCertificateChainErrors = false; + IgnoreRevocationListErrors = false; + + TrustedIssuerCertificates = null; + TrustedPeerCertificates = null; + RejectedCertificateStore = null; } /// @@ -189,26 +169,26 @@ public MqttTlsOptions() /// The key value pairs representing the values from which to construct MqttTlsOptions public MqttTlsOptions(KeyValuePairCollection kvpMqttOptions) { - m_certificates = new MqttTlsCertificates(kvpMqttOptions); + Certificates = new MqttTlsCertificates(kvpMqttOptions); QualifiedName qSslProtocolVersion = EnumMqttClientConfigurationParameters.TlsProtocolVersion.ToString(); - m_SslProtocolVersion = (SslProtocols)Convert.ToInt32(kvpMqttOptions.Find(kvp => kvp.Key.Name.Equals(qSslProtocolVersion.Name))?.Value.Value); + SslProtocolVersion = (SslProtocols)Convert.ToInt32(kvpMqttOptions.Find(kvp => kvp.Key.Name.Equals(qSslProtocolVersion.Name))?.Value.Value); QualifiedName qAllowUntrustedCertificates = EnumMqttClientConfigurationParameters.TlsAllowUntrustedCertificates.ToString(); - m_allowUntrustedCertificates = Convert.ToBoolean(kvpMqttOptions.Find(kvp => kvp.Key.Name.Equals(qAllowUntrustedCertificates.Name))?.Value.Value); + AllowUntrustedCertificates = Convert.ToBoolean(kvpMqttOptions.Find(kvp => kvp.Key.Name.Equals(qAllowUntrustedCertificates.Name))?.Value.Value); QualifiedName qIgnoreCertificateChainErrors = EnumMqttClientConfigurationParameters.TlsIgnoreCertificateChainErrors.ToString(); - m_ignoreCertificateChainErrors = Convert.ToBoolean(kvpMqttOptions.Find(kvp => kvp.Key.Name.Equals(qIgnoreCertificateChainErrors.Name))?.Value.Value); + IgnoreCertificateChainErrors = Convert.ToBoolean(kvpMqttOptions.Find(kvp => kvp.Key.Name.Equals(qIgnoreCertificateChainErrors.Name))?.Value.Value); QualifiedName qIgnoreRevocationListErrors = EnumMqttClientConfigurationParameters.TlsIgnoreRevocationListErrors.ToString(); - m_ignoreRevocationListErrors = Convert.ToBoolean(kvpMqttOptions.Find(kvp => kvp.Key.Name.Equals(qIgnoreRevocationListErrors.Name))?.Value.Value); + IgnoreRevocationListErrors = Convert.ToBoolean(kvpMqttOptions.Find(kvp => kvp.Key.Name.Equals(qIgnoreRevocationListErrors.Name))?.Value.Value); QualifiedName qTrustedIssuerCertificatesStoreType = EnumMqttClientConfigurationParameters.TrustedIssuerCertificatesStoreType.ToString(); string issuerCertificatesStoreType = kvpMqttOptions.Find(kvp => kvp.Key.Name.Equals(qTrustedIssuerCertificatesStoreType.Name))?.Value.Value as string; QualifiedName qTrustedIssuerCertificatesStorePath = EnumMqttClientConfigurationParameters.TrustedIssuerCertificatesStorePath.ToString(); string issuerCertificatesStorePath = kvpMqttOptions.Find(kvp => kvp.Key.Name.Equals(qTrustedIssuerCertificatesStorePath.Name))?.Value.Value as string; - m_trustedIssuerCertificates = new CertificateTrustList { + TrustedIssuerCertificates = new CertificateTrustList { StoreType = issuerCertificatesStoreType, StorePath = issuerCertificatesStorePath }; @@ -218,7 +198,7 @@ public MqttTlsOptions(KeyValuePairCollection kvpMqttOptions) QualifiedName qTrustedPeerCertificatesStorePath = EnumMqttClientConfigurationParameters.TrustedPeerCertificatesStorePath.ToString(); string peerCertificatesStorePath = kvpMqttOptions.Find(kvp => kvp.Key.Name.Equals(qTrustedPeerCertificatesStorePath.Name))?.Value.Value as string; - m_trustedPeerCertificates = new CertificateTrustList { + TrustedPeerCertificates = new CertificateTrustList { StoreType = peerCertificatesStoreType, StorePath = peerCertificatesStorePath }; @@ -228,12 +208,12 @@ public MqttTlsOptions(KeyValuePairCollection kvpMqttOptions) QualifiedName qRejectedCertificateStoreStorePath = EnumMqttClientConfigurationParameters.RejectedCertificateStoreStorePath.ToString(); string rejectedCertificateStoreStorePath = kvpMqttOptions.Find(kvp => kvp.Key.Name.Equals(qRejectedCertificateStoreStorePath.Name))?.Value.Value as string; - m_rejectedCertificateStore = new CertificateTrustList { + RejectedCertificateStore = new CertificateTrustList { StoreType = rejectedCertificateStoreStoreType, StorePath = rejectedCertificateStoreStorePath }; - m_keyValuePairs = kvpMqttOptions; + KeyValuePairs = kvpMqttOptions; } /// @@ -257,79 +237,89 @@ public MqttTlsOptions(MqttTlsCertificates certificates = null, CertificateStoreIdentifier rejectedCertificateStore = null ) { - m_certificates = certificates; - m_SslProtocolVersion = sslProtocolVersion; - m_allowUntrustedCertificates = allowUntrustedCertificates; - m_ignoreCertificateChainErrors = ignoreCertificateChainErrors; - m_ignoreRevocationListErrors = ignoreRevocationListErrors; + Certificates = certificates; + SslProtocolVersion = sslProtocolVersion; + AllowUntrustedCertificates = allowUntrustedCertificates; + IgnoreCertificateChainErrors = ignoreCertificateChainErrors; + IgnoreRevocationListErrors = ignoreRevocationListErrors; - m_trustedIssuerCertificates = trustedIssuerCertificates; - m_trustedPeerCertificates = trustedPeerCertificates; - m_rejectedCertificateStore = rejectedCertificateStore; + TrustedIssuerCertificates = trustedIssuerCertificates; + TrustedPeerCertificates = trustedPeerCertificates; + RejectedCertificateStore = rejectedCertificateStore; - m_keyValuePairs = new KeyValuePairCollection(); + KeyValuePairs = new KeyValuePairCollection(); - if (m_certificates != null) + if (Certificates != null) { - m_keyValuePairs.AddRange(m_certificates.KeyValuePairs); + KeyValuePairs.AddRange(Certificates.KeyValuePairs); } - KeyValuePair kvpTlsProtocolVersion = new KeyValuePair(); - kvpTlsProtocolVersion.Key = EnumMqttClientConfigurationParameters.TlsProtocolVersion.ToString(); - kvpTlsProtocolVersion.Value = (int)m_SslProtocolVersion; - m_keyValuePairs.Add(kvpTlsProtocolVersion); - KeyValuePair kvpAllowUntrustedCertificates = new KeyValuePair(); - kvpAllowUntrustedCertificates.Key = EnumMqttClientConfigurationParameters.TlsAllowUntrustedCertificates.ToString(); - kvpAllowUntrustedCertificates.Value = m_allowUntrustedCertificates; - m_keyValuePairs.Add(kvpAllowUntrustedCertificates); - KeyValuePair kvpIgnoreCertificateChainErrors = new KeyValuePair(); - kvpIgnoreCertificateChainErrors.Key = EnumMqttClientConfigurationParameters.TlsIgnoreCertificateChainErrors.ToString(); - kvpIgnoreCertificateChainErrors.Value = m_ignoreCertificateChainErrors; - m_keyValuePairs.Add(kvpIgnoreCertificateChainErrors); - KeyValuePair kvpIgnoreRevocationListErrors = new KeyValuePair(); - kvpIgnoreRevocationListErrors.Key = EnumMqttClientConfigurationParameters.TlsIgnoreRevocationListErrors.ToString(); - kvpIgnoreRevocationListErrors.Value = m_ignoreRevocationListErrors; - m_keyValuePairs.Add(kvpIgnoreRevocationListErrors); - - KeyValuePair kvpTrustedIssuerCertificatesStoreType = new KeyValuePair(); - kvpTrustedIssuerCertificatesStoreType.Key = EnumMqttClientConfigurationParameters.TrustedIssuerCertificatesStoreType.ToString(); - kvpTrustedIssuerCertificatesStoreType.Value = m_trustedIssuerCertificates?.StoreType; - m_keyValuePairs.Add(kvpTrustedIssuerCertificatesStoreType); - KeyValuePair kvpTrustedIssuerCertificatesStorePath = new KeyValuePair(); - kvpTrustedIssuerCertificatesStorePath.Key = EnumMqttClientConfigurationParameters.TrustedIssuerCertificatesStorePath.ToString(); - kvpTrustedIssuerCertificatesStorePath.Value = m_trustedIssuerCertificates?.StorePath; - m_keyValuePairs.Add(kvpTrustedIssuerCertificatesStorePath); - - KeyValuePair kvpTrustedPeerCertificatesStoreType = new KeyValuePair(); - kvpTrustedPeerCertificatesStoreType.Key = EnumMqttClientConfigurationParameters.TrustedPeerCertificatesStoreType.ToString(); - kvpTrustedPeerCertificatesStoreType.Value = m_trustedPeerCertificates?.StoreType; - m_keyValuePairs.Add(kvpTrustedPeerCertificatesStoreType); - KeyValuePair kvpTrustedPeerCertificatesStorePath = new KeyValuePair(); - kvpTrustedPeerCertificatesStorePath.Key = EnumMqttClientConfigurationParameters.TrustedPeerCertificatesStorePath.ToString(); - kvpTrustedPeerCertificatesStorePath.Value = m_trustedPeerCertificates?.StorePath; - m_keyValuePairs.Add(kvpTrustedPeerCertificatesStorePath); - - KeyValuePair kvpRejectedCertificateStoreStoreType = new KeyValuePair(); - kvpRejectedCertificateStoreStoreType.Key = EnumMqttClientConfigurationParameters.RejectedCertificateStoreStoreType.ToString(); - kvpRejectedCertificateStoreStoreType.Value = m_rejectedCertificateStore?.StoreType; - m_keyValuePairs.Add(kvpRejectedCertificateStoreStoreType); - KeyValuePair kvpRejectedCertificateStoreStorePath = new KeyValuePair(); - kvpRejectedCertificateStoreStorePath.Key = EnumMqttClientConfigurationParameters.RejectedCertificateStoreStorePath.ToString(); - kvpRejectedCertificateStoreStorePath.Value = m_rejectedCertificateStore?.StorePath; - m_keyValuePairs.Add(kvpRejectedCertificateStoreStorePath); + var kvpTlsProtocolVersion = new KeyValuePair { + Key = EnumMqttClientConfigurationParameters.TlsProtocolVersion.ToString(), + Value = (int)SslProtocolVersion + }; + KeyValuePairs.Add(kvpTlsProtocolVersion); + var kvpAllowUntrustedCertificates = new KeyValuePair { + Key = EnumMqttClientConfigurationParameters.TlsAllowUntrustedCertificates.ToString(), + Value = AllowUntrustedCertificates + }; + KeyValuePairs.Add(kvpAllowUntrustedCertificates); + var kvpIgnoreCertificateChainErrors = new KeyValuePair { + Key = EnumMqttClientConfigurationParameters.TlsIgnoreCertificateChainErrors.ToString(), + Value = IgnoreCertificateChainErrors + }; + KeyValuePairs.Add(kvpIgnoreCertificateChainErrors); + var kvpIgnoreRevocationListErrors = new KeyValuePair { + Key = EnumMqttClientConfigurationParameters.TlsIgnoreRevocationListErrors.ToString(), + Value = IgnoreRevocationListErrors + }; + KeyValuePairs.Add(kvpIgnoreRevocationListErrors); + + var kvpTrustedIssuerCertificatesStoreType = new KeyValuePair { + Key = EnumMqttClientConfigurationParameters.TrustedIssuerCertificatesStoreType.ToString(), + Value = TrustedIssuerCertificates?.StoreType + }; + KeyValuePairs.Add(kvpTrustedIssuerCertificatesStoreType); + var kvpTrustedIssuerCertificatesStorePath = new KeyValuePair { + Key = EnumMqttClientConfigurationParameters.TrustedIssuerCertificatesStorePath.ToString(), + Value = TrustedIssuerCertificates?.StorePath + }; + KeyValuePairs.Add(kvpTrustedIssuerCertificatesStorePath); + + var kvpTrustedPeerCertificatesStoreType = new KeyValuePair { + Key = EnumMqttClientConfigurationParameters.TrustedPeerCertificatesStoreType.ToString(), + Value = TrustedPeerCertificates?.StoreType + }; + KeyValuePairs.Add(kvpTrustedPeerCertificatesStoreType); + var kvpTrustedPeerCertificatesStorePath = new KeyValuePair { + Key = EnumMqttClientConfigurationParameters.TrustedPeerCertificatesStorePath.ToString(), + Value = TrustedPeerCertificates?.StorePath + }; + KeyValuePairs.Add(kvpTrustedPeerCertificatesStorePath); + + var kvpRejectedCertificateStoreStoreType = new KeyValuePair { + Key = EnumMqttClientConfigurationParameters.RejectedCertificateStoreStoreType.ToString(), + Value = RejectedCertificateStore?.StoreType + }; + KeyValuePairs.Add(kvpRejectedCertificateStoreStoreType); + var kvpRejectedCertificateStoreStorePath = new KeyValuePair { + Key = EnumMqttClientConfigurationParameters.RejectedCertificateStoreStorePath.ToString(), + Value = RejectedCertificateStore?.StorePath + }; + KeyValuePairs.Add(kvpRejectedCertificateStoreStorePath); } #endregion #region Internal Properties - internal MqttTlsCertificates Certificates { get => m_certificates; set => m_certificates = value; } - internal SslProtocols SslProtocolVersion { get => m_SslProtocolVersion; set => m_SslProtocolVersion = value; } - internal bool AllowUntrustedCertificates { get => m_allowUntrustedCertificates; set => m_allowUntrustedCertificates = value; } - internal bool IgnoreCertificateChainErrors { get => m_ignoreCertificateChainErrors; set => m_ignoreCertificateChainErrors = value; } - internal bool IgnoreRevocationListErrors { get => m_ignoreRevocationListErrors; set => m_ignoreRevocationListErrors = value; } - internal CertificateStoreIdentifier TrustedIssuerCertificates { get => m_trustedIssuerCertificates; set => m_trustedIssuerCertificates = value; } - internal CertificateStoreIdentifier TrustedPeerCertificates { get => m_trustedPeerCertificates; set => m_trustedPeerCertificates = value; } - internal CertificateStoreIdentifier RejectedCertificateStore { get => m_rejectedCertificateStore; set => m_rejectedCertificateStore = value; } - internal KeyValuePairCollection KeyValuePairs { get => m_keyValuePairs; set => m_keyValuePairs = value; } + internal MqttTlsCertificates Certificates { get; set; } + internal SslProtocols SslProtocolVersion { get; set; } + internal bool AllowUntrustedCertificates { get; set; } + internal bool IgnoreCertificateChainErrors { get; set; } + internal bool IgnoreRevocationListErrors { get; set; } + internal CertificateStoreIdentifier TrustedIssuerCertificates { get; set; } + internal CertificateStoreIdentifier TrustedPeerCertificates { get; set; } + internal CertificateStoreIdentifier RejectedCertificateStore { get; set; } + internal KeyValuePairCollection KeyValuePairs { get; set; } #endregion } @@ -339,13 +329,6 @@ public MqttTlsOptions(MqttTlsCertificates certificates = null, public class MqttClientProtocolConfiguration : ITransportProtocolConfiguration { #region Private - SecureString m_userName; - SecureString m_password; - string m_azureClientId; - bool m_cleanSession; - EnumMqttProtocolVersion m_protocolVersion; - MqttTlsOptions m_mqttTlsOptions; - KeyValuePairCollection m_connectionProperties; #endregion #region Constructor @@ -354,13 +337,13 @@ public class MqttClientProtocolConfiguration : ITransportProtocolConfiguration /// public MqttClientProtocolConfiguration() { - m_userName = null; - m_password = null; - m_azureClientId = null; - m_cleanSession = true; - m_protocolVersion = EnumMqttProtocolVersion.V310; - m_mqttTlsOptions = null; - m_connectionProperties = null; + UserName = null; + Password = null; + AzureClientId = null; + CleanSession = true; + ProtocolVersion = EnumMqttProtocolVersion.V310; + MqttTlsOptions = null; + ConnectionProperties = null; } /// @@ -379,39 +362,44 @@ public MqttClientProtocolConfiguration(SecureString userName = null, EnumMqttProtocolVersion version = EnumMqttProtocolVersion.V310, MqttTlsOptions mqttTlsOptions = null) { - m_userName = userName; - m_password = password; - m_azureClientId = azureClientId; - m_cleanSession = cleanSession; - m_protocolVersion = version; - m_mqttTlsOptions = mqttTlsOptions; - - m_connectionProperties = new KeyValuePairCollection(); - - KeyValuePair kvpUserName = new KeyValuePair(); - kvpUserName.Key = EnumMqttClientConfigurationParameters.UserName.ToString(); - kvpUserName.Value = new System.Net.NetworkCredential(string.Empty, m_userName).Password; - m_connectionProperties.Add(kvpUserName); - KeyValuePair kvpPassword = new KeyValuePair(); - kvpPassword.Key = EnumMqttClientConfigurationParameters.Password.ToString(); - kvpPassword.Value = new System.Net.NetworkCredential(string.Empty, m_password).Password; - m_connectionProperties.Add(kvpPassword); - KeyValuePair kvpAzureClientId = new KeyValuePair(); - kvpAzureClientId.Key = EnumMqttClientConfigurationParameters.AzureClientId.ToString(); - kvpAzureClientId.Value = m_azureClientId; - m_connectionProperties.Add(kvpAzureClientId); - KeyValuePair kvpCleanSession = new KeyValuePair(); - kvpCleanSession.Key = EnumMqttClientConfigurationParameters.CleanSession.ToString(); - kvpCleanSession.Value = m_cleanSession; - m_connectionProperties.Add(kvpCleanSession); - KeyValuePair kvpProtocolVersion = new KeyValuePair(); - kvpProtocolVersion.Key = EnumMqttClientConfigurationParameters.ProtocolVersion.ToString(); - kvpProtocolVersion.Value = (int)m_protocolVersion; - m_connectionProperties.Add(kvpProtocolVersion); - - if (m_mqttTlsOptions != null) + UserName = userName; + Password = password; + AzureClientId = azureClientId; + CleanSession = cleanSession; + ProtocolVersion = version; + MqttTlsOptions = mqttTlsOptions; + + ConnectionProperties = new KeyValuePairCollection(); + + var kvpUserName = new KeyValuePair { + Key = EnumMqttClientConfigurationParameters.UserName.ToString(), + Value = new System.Net.NetworkCredential(string.Empty, UserName).Password + }; + ConnectionProperties.Add(kvpUserName); + var kvpPassword = new KeyValuePair { + Key = EnumMqttClientConfigurationParameters.Password.ToString(), + Value = new System.Net.NetworkCredential(string.Empty, Password).Password + }; + ConnectionProperties.Add(kvpPassword); + var kvpAzureClientId = new KeyValuePair { + Key = EnumMqttClientConfigurationParameters.AzureClientId.ToString(), + Value = AzureClientId + }; + ConnectionProperties.Add(kvpAzureClientId); + var kvpCleanSession = new KeyValuePair { + Key = EnumMqttClientConfigurationParameters.CleanSession.ToString(), + Value = CleanSession + }; + ConnectionProperties.Add(kvpCleanSession); + var kvpProtocolVersion = new KeyValuePair { + Key = EnumMqttClientConfigurationParameters.ProtocolVersion.ToString(), + Value = (int)ProtocolVersion + }; + ConnectionProperties.Add(kvpProtocolVersion); + + if (MqttTlsOptions != null) { - m_connectionProperties.AddRange(m_mqttTlsOptions.KeyValuePairs); + ConnectionProperties.AddRange(MqttTlsOptions.KeyValuePairs); } } @@ -421,71 +409,69 @@ public MqttClientProtocolConfiguration(SecureString userName = null, /// public MqttClientProtocolConfiguration(KeyValuePairCollection connectionProperties) { - m_userName = new SecureString(); + UserName = new SecureString(); QualifiedName qUserName = EnumMqttClientConfigurationParameters.UserName.ToString(); - string sUserName = connectionProperties.Find(kvp => kvp.Key.Name.Equals(qUserName.Name))?.Value.Value as string; - if (sUserName != null) + if (connectionProperties.Find(kvp => kvp.Key.Name.Equals(qUserName.Name))?.Value.Value is string sUserName) { foreach (char c in sUserName?.ToCharArray()) { - m_userName.AppendChar(c); + UserName.AppendChar(c); } } - m_password = new SecureString(); + Password = new SecureString(); QualifiedName qPassword = EnumMqttClientConfigurationParameters.Password.ToString(); - string sPassword = connectionProperties.Find(kvp => kvp.Key.Name.Equals(qPassword.Name))?.Value.Value as string; - if (sPassword != null) + if (connectionProperties.Find(kvp => kvp.Key.Name.Equals(qPassword.Name))?.Value.Value is string sPassword) { foreach (char c in sPassword?.ToCharArray()) { - m_password.AppendChar(c); + Password.AppendChar(c); } } QualifiedName qAzureClientId = EnumMqttClientConfigurationParameters.AzureClientId.ToString(); - m_azureClientId = Convert.ToString(connectionProperties.Find(kvp => kvp.Key.Name.Equals(qAzureClientId.Name))?.Value.Value); + AzureClientId = Convert.ToString(connectionProperties.Find(kvp => kvp.Key.Name.Equals(qAzureClientId.Name))?.Value.Value); QualifiedName qCleanSession = EnumMqttClientConfigurationParameters.CleanSession.ToString(); - m_cleanSession = Convert.ToBoolean(connectionProperties.Find(kvp => kvp.Key.Name.Equals(qCleanSession.Name))?.Value.Value); + CleanSession = Convert.ToBoolean(connectionProperties.Find(kvp => kvp.Key.Name.Equals(qCleanSession.Name))?.Value.Value); QualifiedName qProtocolVersion = EnumMqttClientConfigurationParameters.ProtocolVersion.ToString(); - m_protocolVersion = (EnumMqttProtocolVersion)Convert.ToInt32(connectionProperties.Find(kvp => kvp.Key.Name.Equals(qProtocolVersion.Name))?.Value.Value); - if (m_protocolVersion == EnumMqttProtocolVersion.Unknown) + ProtocolVersion = (EnumMqttProtocolVersion)Convert.ToInt32(connectionProperties.Find(kvp => kvp.Key.Name.Equals(qProtocolVersion.Name))?.Value.Value); + if (ProtocolVersion == EnumMqttProtocolVersion.Unknown) { Utils.Trace(Utils.TraceMasks.Information, "Mqtt protocol version is Unknown and it will default to V310"); - m_protocolVersion = EnumMqttProtocolVersion.V310; + ProtocolVersion = EnumMqttProtocolVersion.V310; } - m_mqttTlsOptions = new MqttTlsOptions(connectionProperties); + MqttTlsOptions = new MqttTlsOptions(connectionProperties); - m_connectionProperties = connectionProperties; + ConnectionProperties = connectionProperties; } #endregion #region Internal Properties - internal SecureString UserName { get => m_userName; set => m_userName = value; } + internal SecureString UserName { get; set; } - internal SecureString Password { get => m_password; set => m_password = value; } + internal SecureString Password { get; set; } - internal string AzureClientId { get => m_azureClientId; set => m_azureClientId = value; } + internal string AzureClientId { get; set; } - internal bool CleanSession { get => m_cleanSession; set => m_cleanSession = value; } + internal bool CleanSession { get; set; } - internal bool UseCredentials { get => (m_userName != null) && (m_userName.Length != 0); } + internal bool UseCredentials => (UserName != null) && (UserName.Length != 0); - internal bool UseAzureClientId { get => (m_azureClientId != null) && (m_azureClientId.Length != 0); } + internal bool UseAzureClientId { get => (AzureClientId != null) && (AzureClientId.Length != 0); } - internal EnumMqttProtocolVersion ProtocolVersion { get => m_protocolVersion; set => m_protocolVersion = value; } + internal EnumMqttProtocolVersion ProtocolVersion { get; set; } - internal MqttTlsOptions MqttTlsOptions { get => m_mqttTlsOptions; set => m_mqttTlsOptions = value; } + internal MqttTlsOptions MqttTlsOptions { get; set; } #endregion Internal Properties #region Public Properties /// /// The key value pairs representing the parameters of a MqttClientProtocolConfiguration /// - public KeyValuePairCollection ConnectionProperties { get => m_connectionProperties; set => m_connectionProperties = value; } + public KeyValuePairCollection ConnectionProperties { get; set; } #endregion Public Propertis } } diff --git a/Libraries/Opc.Ua.PubSub/Transport/MqttPubSubConnection.cs b/Libraries/Opc.Ua.PubSub/Transport/MqttPubSubConnection.cs index 845a51787..8e50b76c4 100644 --- a/Libraries/Opc.Ua.PubSub/Transport/MqttPubSubConnection.cs +++ b/Libraries/Opc.Ua.PubSub/Transport/MqttPubSubConnection.cs @@ -83,7 +83,7 @@ internal class MqttPubSubConnection : UaPubSubConnection, IMqttPubSubConnection /// /// Value in seconds with which to surpass the max keep alive value found. /// - private readonly int MaxKeepAliveIncrement = 5; + private readonly int m_maxKeepAliveIncrement = 5; #endregion #region Constructor @@ -124,14 +124,9 @@ public MqttPubSubConnection(UaPubSubApplication uaPubSubApplication, PubSubConne public bool CanPublishMetaData(WriterGroupDataType writerGroupConfiguration, DataSetWriterDataType dataSetWriter) { - if (!CanPublish(writerGroupConfiguration)) return false; - - if (Application.UaPubSubConfigurator.FindStateForObject(dataSetWriter) != PubSubState.Operational) - { - return false; - } - - return true; + return !CanPublish(writerGroupConfiguration) + ? false + : Application.UaPubSubConfigurator.FindStateForObject(dataSetWriter) == PubSubState.Operational; } /// @@ -139,11 +134,7 @@ public bool CanPublishMetaData(WriterGroupDataType writerGroupConfiguration, /// public override IList CreateNetworkMessages(WriterGroupDataType writerGroupConfiguration, WriterGroupPublishState state) { - BrokerWriterGroupTransportDataType transportSettings = - ExtensionObject.ToEncodeable(writerGroupConfiguration.TransportSettings) - as BrokerWriterGroupTransportDataType; - - if (transportSettings == null) + if (!(ExtensionObject.ToEncodeable(writerGroupConfiguration.TransportSettings) is BrokerWriterGroupTransportDataType)) { //Wrong configuration of writer group MessageSettings return null; @@ -157,7 +148,7 @@ public override IList CreateNetworkMessages(WriterGroupDataTyp // no other encoding is implemented return null; } - + /// /// Create and return the DataSetMetaData message for a DataSetWriter /// @@ -188,7 +179,7 @@ public override bool PublishNetworkMessage(UaNetworkMessage networkMessage) try { - lock (m_lock) + lock (Lock) { if (m_publisherMqttClient != null && m_publisherMqttClient.IsConnected) { @@ -203,16 +194,13 @@ public override bool PublishNetworkMessage(UaNetworkMessage networkMessage) // the network messages that have DataSetWriterId are either metaData messages or SingleDataSet messages and if (networkMessage.DataSetWriterId != null) { - var dataSetWriter = networkMessage.WriterGroupConfiguration.DataSetWriters + DataSetWriterDataType dataSetWriter = networkMessage.WriterGroupConfiguration.DataSetWriters .Find(x => x.DataSetWriterId == networkMessage.DataSetWriterId); if (dataSetWriter != null) - { - var transportSettings = ExtensionObject - .ToEncodeable(dataSetWriter.TransportSettings) - as BrokerDataSetWriterTransportDataType; - - if (transportSettings != null) + { + if (ExtensionObject + .ToEncodeable(dataSetWriter.TransportSettings) is BrokerDataSetWriterTransportDataType transportSettings) { qos = transportSettings.RequestedDeliveryGuarantee; @@ -223,11 +211,8 @@ public override bool PublishNetworkMessage(UaNetworkMessage networkMessage) if (queueName == null || qos == BrokerTransportQualityOfService.NotSpecified) { - var transportSettings = ExtensionObject.ToEncodeable( - networkMessage.WriterGroupConfiguration.TransportSettings) - as BrokerWriterGroupTransportDataType; - - if (transportSettings != null) + if (ExtensionObject.ToEncodeable( + networkMessage.WriterGroupConfiguration.TransportSettings) is BrokerWriterGroupTransportDataType transportSettings) { if (queueName == null) { @@ -290,14 +275,12 @@ public override bool AreClientsConnected() protected override async Task InternalStart() { //cleanup all existing MQTT connections previously open - await InternalStop().ConfigureAwait(false); + await InternalStop().ConfigureAwait(false); - lock (m_lock) + lock (Lock) { - NetworkAddressUrlDataType networkAddressUrlState = ExtensionObject.ToEncodeable( - PubSubConnectionConfiguration.Address) as NetworkAddressUrlDataType; - - if (networkAddressUrlState == null) + if (!(ExtensionObject.ToEncodeable( + PubSubConnectionConfiguration.Address) is NetworkAddressUrlDataType networkAddressUrlState)) { Utils.Trace( Utils.TraceMasks.Error, @@ -339,12 +322,15 @@ protected override async Task InternalStart() { foreach (DataSetWriterDataType dataSetWriter in writerGroup.DataSetWriters) { - if (dataSetWriter.DataSetWriterId == 0) continue; - - BrokerDataSetWriterTransportDataType transport = ExtensionObject.ToEncodeable(dataSetWriter.TransportSettings) - as BrokerDataSetWriterTransportDataType; + if (dataSetWriter.DataSetWriterId == 0) + { + continue; + } - if (transport == null || transport.MetaDataUpdateTime == 0) continue; + if (!(ExtensionObject.ToEncodeable(dataSetWriter.TransportSettings) is BrokerDataSetWriterTransportDataType transport) || transport.MetaDataUpdateTime == 0) + { + continue; + } m_metaDataPublishers.Add(new MqttMetadataPublisher(this, writerGroup, dataSetWriter, transport.MetaDataUpdateTime)); } @@ -377,26 +363,23 @@ protected override async Task InternalStart() if (nrOfSubscribers > 0) { // collect all topics from all ReaderGroups - StringCollection topics = new StringCollection(); - foreach (var readerGroup in PubSubConnectionConfiguration.ReaderGroups) + var topics = new StringCollection(); + foreach (ReaderGroupDataType readerGroup in PubSubConnectionConfiguration.ReaderGroups) { if (!readerGroup.Enabled) { continue; } - foreach (var dataSetReader in readerGroup.DataSetReaders) + foreach (DataSetReaderDataType dataSetReader in readerGroup.DataSetReaders) { if (!dataSetReader.Enabled) { continue; } - BrokerDataSetReaderTransportDataType brokerTransportSettings = - ExtensionObject.ToEncodeable(dataSetReader.TransportSettings) - as BrokerDataSetReaderTransportDataType; - if (brokerTransportSettings != null && !topics.Contains(brokerTransportSettings.QueueName)) + if (ExtensionObject.ToEncodeable(dataSetReader.TransportSettings) is BrokerDataSetReaderTransportDataType brokerTransportSettings && !topics.Contains(brokerTransportSettings.QueueName)) { topics.Add(brokerTransportSettings.QueueName); @@ -415,7 +398,7 @@ protected override async Task InternalStart() topics).ConfigureAwait(false); } - lock (m_lock) + lock (Lock) { m_publisherMqttClient = publisherClient; m_subscriberMqttClient = subscriberClient; @@ -430,32 +413,41 @@ protected override async Task InternalStart() /// protected override async Task InternalStop() { - var publisherMqttClient = m_publisherMqttClient; - var subscriberMqttClient = m_subscriberMqttClient; + IMqttClient publisherMqttClient = m_publisherMqttClient; + IMqttClient subscriberMqttClient = m_subscriberMqttClient; - if (publisherMqttClient != null) + void DisposeCerts(X509CertificateCollection certificates) { - if (publisherMqttClient.IsConnected) - { - await publisherMqttClient.DisconnectAsync().ContinueWith((e) => publisherMqttClient.Dispose()).ConfigureAwait(false); - } - else + if (certificates != null) { - publisherMqttClient.Dispose(); + // dispose certificates + foreach (X509Certificate cert in certificates) + { + Utils.SilentDispose(cert); + } } } - - if (subscriberMqttClient != null) + async Task InternalStop(IMqttClient client) { - if (subscriberMqttClient.IsConnected) - { - await subscriberMqttClient.DisconnectAsync().ContinueWith((e) => subscriberMqttClient.Dispose()).ConfigureAwait(false); - } - else + if (client != null) { - subscriberMqttClient.Dispose(); + X509CertificateCollection certificates = client.Options?.ChannelOptions?.TlsOptions?.ClientCertificatesProvider?.GetCertificates(); + if (client.IsConnected) + { + await client.DisconnectAsync().ContinueWith((e) => { + DisposeCerts(certificates); + Utils.SilentDispose(client); + }).ConfigureAwait(false); + } + else + { + DisposeCerts(certificates); + Utils.SilentDispose(client); + } } } + await InternalStop(publisherMqttClient).ConfigureAwait(false); + await InternalStop(subscriberMqttClient).ConfigureAwait(false); if (m_metaDataPublishers != null) { @@ -466,7 +458,7 @@ protected override async Task InternalStop() m_metaDataPublishers.Clear(); } - lock (m_lock) + lock (Lock) { m_publisherMqttClient = null; m_subscriberMqttClient = null; @@ -477,15 +469,15 @@ protected override async Task InternalStop() #region Private Methods - private bool MatchTopic(string pattern, string topic) + private static bool MatchTopic(string pattern, string topic) { if (String.IsNullOrEmpty(pattern) || pattern == "#") { return true; } - var fields1 = pattern.Split('/'); - var fields2 = topic.Split('/'); + string[] fields1 = pattern.Split('/'); + string[] fields2 = topic.Split('/'); for (int ii = 0; ii < fields1.Length && ii < fields2.Length; ii++) { @@ -514,12 +506,15 @@ private Task ProcessMqttMessage(MqttApplicationMessageReceivedEventArgs eventArg Utils.Trace("MQTTConnection - ProcessMqttMessage() received from topic={0}", topic); // get the datasetreaders for received message topic - List dataSetReaders = new List(); + var dataSetReaders = new List(); foreach (DataSetReaderDataType dsReader in GetOperationalDataSetReaders()) { - if (dsReader == null) continue; + if (dsReader == null) + { + continue; + } - BrokerDataSetReaderTransportDataType brokerDataSetReaderTransportDataType = + var brokerDataSetReaderTransportDataType = ExtensionObject.ToEncodeable(dsReader.TransportSettings) as BrokerDataSetReaderTransportDataType; @@ -547,10 +542,10 @@ private Task ProcessMqttMessage(MqttApplicationMessageReceivedEventArgs eventArg if (dataSetReaders.Count > 0) { // raise RawData received event - RawDataReceivedEventArgs rawDataReceivedEventArgs = new RawDataReceivedEventArgs() { + var rawDataReceivedEventArgs = new RawDataReceivedEventArgs() { Message = eventArgs.ApplicationMessage.PayloadSegment.Array, Source = topic, - TransportProtocol = this.TransportProtocol, + TransportProtocol = TransportProtocol, MessageMapping = m_messageMapping, PubSubConnectionConfiguration = PubSubConnectionConfiguration }; @@ -567,7 +562,7 @@ private Task ProcessMqttMessage(MqttApplicationMessageReceivedEventArgs eventArg // initialize the expected NetworkMessage UaNetworkMessage networkMessage = m_messageCreator.CreateNewNetworkMessage(); - + // trigger message decoding if (networkMessage != null) { @@ -590,7 +585,7 @@ private Task ProcessMqttMessage(MqttApplicationMessageReceivedEventArgs eventArg /// /// /// - private MqttQualityOfServiceLevel GetMqttQualityOfServiceLevel(BrokerTransportQualityOfService brokerTransportQualityOfService) + private static MqttQualityOfServiceLevel GetMqttQualityOfServiceLevel(BrokerTransportQualityOfService brokerTransportQualityOfService) { switch (brokerTransportQualityOfService) { @@ -612,12 +607,9 @@ private MqttQualityOfServiceLevel GetMqttQualityOfServiceLevel(BrokerTransportQu private MqttClientOptions GetMqttClientOptions() { MqttClientOptions mqttOptions = null; - TimeSpan mqttKeepAlive = TimeSpan.FromSeconds(GetWriterGroupsMaxKeepAlive() + MaxKeepAliveIncrement); + var mqttKeepAlive = TimeSpan.FromSeconds(GetWriterGroupsMaxKeepAlive() + m_maxKeepAliveIncrement); - NetworkAddressUrlDataType networkAddressUrlState = - ExtensionObject.ToEncodeable(PubSubConnectionConfiguration.Address) - as NetworkAddressUrlDataType; - if (networkAddressUrlState == null) + if (!(ExtensionObject.ToEncodeable(PubSubConnectionConfiguration.Address) is NetworkAddressUrlDataType networkAddressUrlState)) { Utils.Trace(Utils.TraceMasks.Error, "The configuration for mqttConnection {0} has invalid Address configuration.", @@ -655,11 +647,9 @@ private MqttClientOptions GetMqttClientOptions() new MqttClientProtocolConfiguration(PubSubConnectionConfiguration.ConnectionProperties); - MqttClientProtocolConfiguration mqttProtocolConfiguration = - transportProtocolConfiguration as MqttClientProtocolConfiguration; - if (mqttProtocolConfiguration != null) + if (transportProtocolConfiguration is MqttClientProtocolConfiguration mqttProtocolConfiguration) { - MqttProtocolVersion mqttProtocolVersion = + var mqttProtocolVersion = (MqttProtocolVersion)((MqttClientProtocolConfiguration)transportProtocolConfiguration) .ProtocolVersion; // create uniques client id @@ -670,21 +660,29 @@ private MqttClientOptions GetMqttClientOptions() MqttTlsOptions mqttTlsOptions = ((MqttClientProtocolConfiguration)transportProtocolConfiguration).MqttTlsOptions; + var x509Certificate2s = new List(); + if (mqttTlsOptions?.Certificates != null) + { + foreach (X509Certificate x509cert in mqttTlsOptions?.Certificates.X509Certificates) + { + x509Certificate2s.Add(new X509Certificate2(x509cert.Handle)); + } + } + MqttClientOptionsBuilder mqttClientOptionsBuilder = new MqttClientOptionsBuilder() .WithTcpServer(m_brokerHostName, m_brokerPort) .WithKeepAlivePeriod(mqttKeepAlive) .WithProtocolVersion(mqttProtocolVersion) .WithClientId(clientId) - .WithTls(new MqttClientOptionsBuilderTlsParameters { - UseTls = true, - Certificates = mqttTlsOptions?.Certificates?.X509Certificates, - SslProtocol = - mqttTlsOptions?.SslProtocolVersion ?? - System.Security.Authentication.SslProtocols.Tls12, - AllowUntrustedCertificates = mqttTlsOptions?.AllowUntrustedCertificates ?? false, - IgnoreCertificateChainErrors = mqttTlsOptions?.IgnoreCertificateChainErrors ?? false, - IgnoreCertificateRevocationErrors = mqttTlsOptions?.IgnoreRevocationListErrors ?? false, - CertificateValidationHandler = ValidateBrokerCertificate + .WithTlsOptions(o => { + o.UseTls(true); + o.WithClientCertificates(x509Certificate2s); + o.WithSslProtocols(mqttTlsOptions?.SslProtocolVersion ?? + System.Security.Authentication.SslProtocols.None);// Allow OS to choose best option + o.WithAllowUntrustedCertificates(mqttTlsOptions?.AllowUntrustedCertificates ?? false); + o.WithIgnoreCertificateChainErrors(mqttTlsOptions?.IgnoreCertificateChainErrors ?? false); + o.WithIgnoreCertificateRevocationErrors(mqttTlsOptions?.IgnoreRevocationListErrors ?? false); + o.WithCertificateValidationHandler(ValidateBrokerCertificate); }); // Set user credentials. @@ -741,18 +739,19 @@ private MqttClientOptions GetMqttClientOptions() /// /// /// A new instance of stack validator - private CertificateValidator CreateCertificateValidator(MqttTlsOptions mqttTlsOptions) + private static CertificateValidator CreateCertificateValidator(MqttTlsOptions mqttTlsOptions) { - CertificateValidator certificateValidator = new CertificateValidator(); + var certificateValidator = new CertificateValidator(); - SecurityConfiguration securityConfiguration = new SecurityConfiguration(); - securityConfiguration.TrustedIssuerCertificates = (CertificateTrustList)mqttTlsOptions.TrustedIssuerCertificates; - securityConfiguration.TrustedPeerCertificates = (CertificateTrustList)mqttTlsOptions.TrustedPeerCertificates; - securityConfiguration.RejectedCertificateStore = mqttTlsOptions.RejectedCertificateStore; + var securityConfiguration = new SecurityConfiguration { + TrustedIssuerCertificates = (CertificateTrustList)mqttTlsOptions.TrustedIssuerCertificates, + TrustedPeerCertificates = (CertificateTrustList)mqttTlsOptions.TrustedPeerCertificates, + RejectedCertificateStore = mqttTlsOptions.RejectedCertificateStore, - securityConfiguration.RejectSHA1SignedCertificates = true; - securityConfiguration.AutoAcceptUntrustedCertificates = mqttTlsOptions.AllowUntrustedCertificates; - securityConfiguration.RejectUnknownRevocationStatus = !mqttTlsOptions.IgnoreRevocationListErrors; + RejectSHA1SignedCertificates = true, + AutoAcceptUntrustedCertificates = mqttTlsOptions.AllowUntrustedCertificates, + RejectUnknownRevocationStatus = !mqttTlsOptions.IgnoreRevocationListErrors + }; certificateValidator.Update(securityConfiguration).Wait(); @@ -765,7 +764,7 @@ private CertificateValidator CreateCertificateValidator(MqttTlsOptions mqttTlsOp /// The context of the validation private bool ValidateBrokerCertificate(MqttClientCertificateValidationEventArgs context) { - X509Certificate2 brokerCertificate = new X509Certificate2(context.Certificate.GetRawCertData()); + var brokerCertificate = new X509Certificate2(context.Certificate.GetRawCertData()); try { @@ -781,7 +780,7 @@ private bool ValidateBrokerCertificate(MqttClientCertificateValidationEventArgs } catch (Exception ex) { - Utils.Trace(ex,"Connection '{0}' - Broker certificate '{1}' rejected.", + Utils.Trace(ex, "Connection '{0}' - Broker certificate '{1}' rejected.", PubSubConnectionConfiguration.Name, brokerCertificate.Subject); return false; } @@ -827,7 +826,7 @@ private void CertificateValidator_CertificateValidation(CertificateValidator sen Utils.Trace(ex, "MqttPubSubConnection.CertificateValidation error."); } } - + #endregion Private methods #region MessageCreator innner classes @@ -891,18 +890,16 @@ public override UaNetworkMessage CreateNewNetworkMessage() public override IList CreateNetworkMessages(WriterGroupDataType writerGroupConfiguration, WriterGroupPublishState state) { - JsonWriterGroupMessageDataType jsonMessageSettings = ExtensionObject.ToEncodeable( - writerGroupConfiguration.MessageSettings) - as JsonWriterGroupMessageDataType; - if (jsonMessageSettings == null) + if (!(ExtensionObject.ToEncodeable( + writerGroupConfiguration.MessageSettings) is JsonWriterGroupMessageDataType jsonMessageSettings)) { //Wrong configuration of writer group MessageSettings return null; } //Create list of dataSet messages to be sent - List jsonDataSetMessages = new List(); - List networkMessages = new List(); + var jsonDataSetMessages = new List(); + var networkMessages = new List(); foreach (DataSetWriterDataType dataSetWriter in writerGroupConfiguration.DataSetWriters) { @@ -920,13 +917,12 @@ public override IList CreateNetworkMessages(WriterGroupDataTyp { networkMessages.Add(CreateDataSetMetaDataNetworkMessage(writerGroupConfiguration, dataSetWriter.DataSetWriterId, dataSet.DataSetMetaData)); } - - JsonDataSetWriterMessageDataType jsonDataSetMessageSettings = - ExtensionObject.ToEncodeable(dataSetWriter.MessageSettings) as JsonDataSetWriterMessageDataType; - if (jsonDataSetMessageSettings != null) + + if (ExtensionObject.ToEncodeable(dataSetWriter.MessageSettings) is JsonDataSetWriterMessageDataType jsonDataSetMessageSettings) { - JsonDataSetMessage jsonDataSetMessage = new JsonDataSetMessage(dataSet); - jsonDataSetMessage.DataSetMessageContentMask = (JsonDataSetMessageContentMask)jsonDataSetMessageSettings.DataSetMessageContentMask; + var jsonDataSetMessage = new JsonDataSetMessage(dataSet) { + DataSetMessageContentMask = (JsonDataSetMessageContentMask)jsonDataSetMessageSettings.DataSetMessageContentMask + }; // set common properties of dataset message jsonDataSetMessage.SetFieldContentMask((DataSetFieldContentMask)dataSetWriter.DataSetFieldContentMask); @@ -952,7 +948,7 @@ public override IList CreateNetworkMessages(WriterGroupDataTyp } // each entry of this list will generate a network message - List> dataSetMessagesList = new List>(); + var dataSetMessagesList = new List>(); if ((((JsonNetworkMessageContentMask)jsonMessageSettings.NetworkMessageContentMask) & JsonNetworkMessageContentMask.SingleDataSetMessage) != 0) { // create a new network message for each dataset @@ -968,7 +964,7 @@ public override IList CreateNetworkMessages(WriterGroupDataTyp foreach (List dataSetMessagesToUse in dataSetMessagesList) { - JsonNetworkMessage jsonNetworkMessage = new JsonNetworkMessage(writerGroupConfiguration, dataSetMessagesToUse); + var jsonNetworkMessage = new JsonNetworkMessage(writerGroupConfiguration, dataSetMessagesToUse); jsonNetworkMessage.SetNetworkMessageContentMask((JsonNetworkMessageContentMask)jsonMessageSettings?.NetworkMessageContentMask); // Network message header @@ -1026,19 +1022,16 @@ public override UaNetworkMessage CreateNewNetworkMessage() public override IList CreateNetworkMessages(WriterGroupDataType writerGroupConfiguration, WriterGroupPublishState state) { - UadpWriterGroupMessageDataType uadpMessageSettings = ExtensionObject.ToEncodeable( - writerGroupConfiguration.MessageSettings) - as UadpWriterGroupMessageDataType; - - if (uadpMessageSettings == null) + if (!(ExtensionObject.ToEncodeable( + writerGroupConfiguration.MessageSettings) is UadpWriterGroupMessageDataType uadpMessageSettings)) { //Wrong configuration of writer group MessageSettings return null; } //Create list of dataSet messages to be sent - List uadpDataSetMessages = new List(); - List networkMessages = new List(); + var uadpDataSetMessages = new List(); + var networkMessages = new List(); foreach (DataSetWriterDataType dataSetWriter in writerGroupConfiguration.DataSetWriters) { @@ -1058,13 +1051,10 @@ public override IList CreateNetworkMessages(WriterGroupDataTyp } // try to create Uadp message - UadpDataSetWriterMessageDataType uadpDataSetMessageSettings = - ExtensionObject.ToEncodeable(dataSetWriter.MessageSettings) as - UadpDataSetWriterMessageDataType; // check MessageSettings to see how to encode DataSet - if (uadpDataSetMessageSettings != null) + if (ExtensionObject.ToEncodeable(dataSetWriter.MessageSettings) is UadpDataSetWriterMessageDataType uadpDataSetMessageSettings) { - UadpDataSetMessage uadpDataSetMessage = new UadpDataSetMessage(dataSet); + var uadpDataSetMessage = new UadpDataSetMessage(dataSet); uadpDataSetMessage.SetMessageContentMask((UadpDataSetMessageContentMask)uadpDataSetMessageSettings.DataSetMessageContentMask); uadpDataSetMessage.ConfiguredSize = uadpDataSetMessageSettings.ConfiguredSize; uadpDataSetMessage.DataSetOffset = uadpDataSetMessageSettings.DataSetOffset; @@ -1093,7 +1083,7 @@ public override IList CreateNetworkMessages(WriterGroupDataTyp return networkMessages; } - UadpNetworkMessage uadpNetworkMessage = + var uadpNetworkMessage = new UadpNetworkMessage(writerGroupConfiguration, uadpDataSetMessages); uadpNetworkMessage.SetNetworkMessageContentMask( (UadpNetworkMessageContentMask)uadpMessageSettings?.NetworkMessageContentMask); diff --git a/Libraries/Opc.Ua.PubSub/Transport/UdpClientCreator.cs b/Libraries/Opc.Ua.PubSub/Transport/UdpClientCreator.cs index 38889f814..407e6bd65 100644 --- a/Libraries/Opc.Ua.PubSub/Transport/UdpClientCreator.cs +++ b/Libraries/Opc.Ua.PubSub/Transport/UdpClientCreator.cs @@ -107,7 +107,7 @@ internal static IPEndPoint GetEndPoint(string url) internal static List GetUdpClients(UsedInContext pubSubContext, string networkInterface, IPEndPoint configuredEndpoint) { StringBuilder buffer = new StringBuilder(); - buffer.AppendFormat("networkAddressUrl.NetworkInterface = {0} \n", networkInterface ?? "null"); + buffer.AppendFormat("networkAddressUrl.NetworkInterface = {0} \n", networkInterface ?? "null"); buffer.AppendFormat("configuredEndpoint = {0}", configuredEndpoint != null ? configuredEndpoint.ToString() : "null"); Utils.Trace(Utils.TraceMasks.Information, buffer.ToString()); diff --git a/Libraries/Opc.Ua.PubSub/Transport/UdpDiscovery.cs b/Libraries/Opc.Ua.PubSub/Transport/UdpDiscovery.cs index d79add99e..41a82ed40 100644 --- a/Libraries/Opc.Ua.PubSub/Transport/UdpDiscovery.cs +++ b/Libraries/Opc.Ua.PubSub/Transport/UdpDiscovery.cs @@ -42,7 +42,7 @@ internal abstract class UdpDiscovery #region Fields private const string kDefaultDiscoveryUrl = "opc.udp://224.0.2.14:4840"; - protected object m_lock = new object(); + protected readonly object m_lock = new object(); protected UdpPubSubConnection m_udpConnection; protected List m_discoveryUdpClients; #endregion @@ -85,14 +85,14 @@ protected UdpDiscovery(UdpPubSubConnection udpConnection) /// The object that should be used in encode/decode messages /// public virtual async Task StartAsync(IServiceMessageContext messageContext) - { + { await Task.Run(() => { lock (m_lock) { MessageContext = messageContext; // initialize Discovery channels - m_discoveryUdpClients = UdpClientCreator.GetUdpClients(UsedInContext.Discovery, DiscoveryNetworkInterfaceName, DiscoveryNetworkAddressEndPoint); + m_discoveryUdpClients = UdpClientCreator.GetUdpClients(UsedInContext.Discovery, DiscoveryNetworkInterfaceName, DiscoveryNetworkAddressEndPoint); } }).ConfigureAwait(false); } @@ -119,7 +119,7 @@ public virtual async Task StopAsync() await Task.CompletedTask.ConfigureAwait(false); } #endregion - + #region Private Methods /// @@ -128,21 +128,17 @@ public virtual async Task StopAsync() private void Initialize() { PubSubConnectionDataType pubSubConnectionConfiguration = m_udpConnection.PubSubConnectionConfiguration; - DatagramConnectionTransportDataType transportSettings = ExtensionObject.ToEncodeable(pubSubConnectionConfiguration.TransportSettings) - as DatagramConnectionTransportDataType; - if (transportSettings != null && transportSettings.DiscoveryAddress != null) + if (ExtensionObject.ToEncodeable(pubSubConnectionConfiguration.TransportSettings) is DatagramConnectionTransportDataType transportSettings && transportSettings.DiscoveryAddress != null) { - NetworkAddressUrlDataType discoveryNetworkAddressUrlState = ExtensionObject.ToEncodeable(transportSettings.DiscoveryAddress) - as NetworkAddressUrlDataType; - if (discoveryNetworkAddressUrlState != null) + if (ExtensionObject.ToEncodeable(transportSettings.DiscoveryAddress) is NetworkAddressUrlDataType discoveryNetworkAddressUrlState) { Utils.Trace(Utils.TraceMasks.Information, "The configuration for connection {0} has custom DiscoveryAddress configuration.", pubSubConnectionConfiguration.Name); DiscoveryNetworkInterfaceName = discoveryNetworkAddressUrlState.NetworkInterface; DiscoveryNetworkAddressEndPoint = UdpClientCreator.GetEndPoint(discoveryNetworkAddressUrlState.Url); - } + } } if (DiscoveryNetworkAddressEndPoint == null) @@ -154,8 +150,8 @@ private void Initialize() } } - - + + #endregion } diff --git a/Libraries/Opc.Ua.PubSub/Transport/UdpDiscoveryPublisher.cs b/Libraries/Opc.Ua.PubSub/Transport/UdpDiscoveryPublisher.cs index 7afc92eb3..706d5340b 100644 --- a/Libraries/Opc.Ua.PubSub/Transport/UdpDiscoveryPublisher.cs +++ b/Libraries/Opc.Ua.PubSub/Transport/UdpDiscoveryPublisher.cs @@ -33,6 +33,7 @@ using System.Net.Sockets; using System.Threading.Tasks; using Opc.Ua.PubSub.Encoding; +using System.Linq; namespace Opc.Ua.PubSub.Transport { @@ -41,11 +42,13 @@ namespace Opc.Ua.PubSub.Transport /// internal class UdpDiscoveryPublisher : UdpDiscovery { + #region Private fields + // Minimum response interval private const int kMinimumResponseInterval = 500; // The list that will store the WriterIds that shall be set as DataSetMetaData Response message private readonly List m_metadataWriterIdsToSend; - private int m_responseInterval = kMinimumResponseInterval; + #endregion #region Constructor /// @@ -86,7 +89,6 @@ public override async Task StartAsync(IServiceMessageContext messageContext) } } } - #endregion #region Private Methods @@ -155,7 +157,7 @@ private void ProcessReceivedMessageDiscovery(byte[] messageBytes, IPEndPoint sou UadpNetworkMessage networkMessage = new UadpNetworkMessage(); // decode the received message networkMessage.Decode(MessageContext, messageBytes, null); - + if (networkMessage.UADPNetworkMessageType == UADPNetworkMessageType.DiscoveryRequest && networkMessage.UADPDiscoveryType == UADPNetworkMessageDiscoveryType.DataSetMetaData && networkMessage.DataSetWriterIds != null) @@ -177,12 +179,28 @@ private void ProcessReceivedMessageDiscovery(byte[] messageBytes, IPEndPoint sou Task.Run(SendResponseDataSetMetaData).ConfigureAwait(false); } + + else if (networkMessage.UADPNetworkMessageType == UADPNetworkMessageType.DiscoveryRequest + && networkMessage.UADPDiscoveryType == UADPNetworkMessageDiscoveryType.PublisherEndpoint) + { + Task.Run(SendResponsePublisherEndpoints).ConfigureAwait(false); + } + + else if (networkMessage.UADPNetworkMessageType == UADPNetworkMessageType.DiscoveryRequest + && networkMessage.UADPDiscoveryType == UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration + && networkMessage.DataSetWriterIds != null) + { + Task.Run(SendResponseDataSetWriterConfiguration).ConfigureAwait(false); + } } + + /// + /// Sends a DataSetMetadata discovery response message + /// private async Task SendResponseDataSetMetaData() { - await Task.Delay(m_responseInterval).ConfigureAwait(false); - + await Task.Delay(kMinimumResponseInterval).ConfigureAwait(false); lock (m_lock) { if (m_metadataWriterIdsToSend.Count > 0) @@ -191,7 +209,7 @@ private async Task SendResponseDataSetMetaData() foreach (UaNetworkMessage message in responseMessages) { - Utils.Trace("UdpDiscoveryPublisher.SendResponseDataSetMetaData Before sending message for DataSetWriterId:{0}", message.DataSetWriterId); + Utils.Trace("UdpDiscoveryPublisher.SendResponseDataSetMetaData before sending message for DataSetWriterId:{0}", message.DataSetWriterId); m_udpConnection.PublishNetworkMessage(message); } @@ -200,6 +218,61 @@ private async Task SendResponseDataSetMetaData() } } + /// + /// Sends a DataSetWriterConfiguration discovery response message + /// + private async Task SendResponseDataSetWriterConfiguration() + { + await Task.Delay(kMinimumResponseInterval).ConfigureAwait(false); + lock (m_lock) + { + IList dataSetWriterIdsToSend = new List(); + if (GetDataSetWriterIds != null) + { + dataSetWriterIdsToSend = GetDataSetWriterIds.Invoke(m_udpConnection.Application); + } + + if (dataSetWriterIdsToSend.Count > 0) + { + IList responsesMessages = m_udpConnection.CreateDataSetWriterCofigurationMessage( + dataSetWriterIdsToSend.ToArray()); + + foreach (var responsesMessage in responsesMessages) + { + Utils.Trace("UdpDiscoveryPublisher.SendResponseDataSetWriterConfiguration Before sending message for DataSetWriterId:{0}", responsesMessage.DataSetWriterId); + + m_udpConnection.PublishNetworkMessage(responsesMessage); + } + } + } + } + + /// + /// Send response PublisherEndpoints + /// + private async Task SendResponsePublisherEndpoints() + { + await Task.Delay(kMinimumResponseInterval).ConfigureAwait(false); + + lock (m_lock) + { + IList publisherEndpointsToSend = new List(); + if (GetPublisherEndpoints != null) + { + publisherEndpointsToSend = GetPublisherEndpoints.Invoke(); + } + + UaNetworkMessage message = m_udpConnection.CreatePublisherEndpointsNetworkMessage( + publisherEndpointsToSend.ToArray(), + publisherEndpointsToSend.Count > 0 ? StatusCodes.Good : StatusCodes.BadNotFound, + m_udpConnection.PubSubConnectionConfiguration.PublisherId.Value); + + Utils.Trace("UdpDiscoveryPublisher.SendResponsePublisherEndpoints before sending message for PublisherEndpoints."); + + m_udpConnection.PublishNetworkMessage(message); + } + } + /// /// Re initializes the socket /// @@ -231,5 +304,19 @@ private void Renew(UdpClient socket) } } #endregion + + #region Public Properties + + /// + /// The GetPublisherEndpoints event callback reference to store the EndpointDescription[] to be set as PublisherEndpoints Response message + /// + public GetPublisherEndpointsEventHandler GetPublisherEndpoints { get; set; } + + /// + /// The GetDataSetWriterIds event callback reference to store the DataSetWriter ids to be set as PublisherEndpoints Response message + /// + public GetDataSetWriterIdsEventHandler GetDataSetWriterIds { get; set; } + + #endregion } } diff --git a/Libraries/Opc.Ua.PubSub/Transport/UdpDiscoverySubscriber.cs b/Libraries/Opc.Ua.PubSub/Transport/UdpDiscoverySubscriber.cs index 4ea036bb9..0d7f0f992 100644 --- a/Libraries/Opc.Ua.PubSub/Transport/UdpDiscoverySubscriber.cs +++ b/Libraries/Opc.Ua.PubSub/Transport/UdpDiscoverySubscriber.cs @@ -29,6 +29,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; using System.Threading.Tasks; using Opc.Ua.PubSub.Encoding; @@ -39,6 +41,7 @@ namespace Opc.Ua.PubSub.Transport /// internal class UdpDiscoverySubscriber : UdpDiscovery { + #region Private Fields private const int kInitialRequestInterval = 5000; // The list that will store the WriterIds that shall be included in a DataSetMetaData Request message @@ -46,6 +49,7 @@ internal class UdpDiscoverySubscriber : UdpDiscovery // the component that triggers the publish request messages private readonly IntervalRunner m_intervalRunner; + #endregion #region Constructor /// @@ -57,13 +61,13 @@ public UdpDiscoverySubscriber(UdpPubSubConnection udpConnection) : base(udpConne m_metadataWriterIdsToSend = new List(); m_intervalRunner = new IntervalRunner(udpConnection.PubSubConnectionConfiguration.Name, - kInitialRequestInterval, CanPublish, SendDiscoveryRequestDataSetMetaData); + kInitialRequestInterval, CanPublish, RequestDiscoveryMessages); } #endregion - #region Start/Stop Method Overides - + #region Start/Stop Method Overrides + /// /// Implementation of StartAsync for the subscriber Discovery /// @@ -99,13 +103,13 @@ public void AddWriterIdForDataSetMetadata(UInt16 writerId) { if (!m_metadataWriterIdsToSend.Contains(writerId)) { - m_metadataWriterIdsToSend.Add(writerId); + m_metadataWriterIdsToSend.Add(writerId); } } } /// - /// Removes the specfoed DataSetWriterId for DataSetInformation to be requested + /// Removes the specified DataSetWriterId for DataSetInformation to be requested /// /// public void RemoveWriterIdForDataSetMetadata(UInt16 writerId) @@ -118,33 +122,91 @@ public void RemoveWriterIdForDataSetMetadata(UInt16 writerId) } } } - #endregion - - #region Private Methods - /// - /// Decide if there is anything to publish + /// Send a discovery Request for DataSetWriterConfiguration /// - /// - private bool CanPublish() + public void SendDiscoveryRequestDataSetWriterConfiguration() { - lock (m_lock) + ushort[] dataSetWriterIds = m_udpConnection.PubSubConnectionConfiguration.ReaderGroups? + .SelectMany(group => group.DataSetReaders)? + .Select(group => group.DataSetWriterId)? + .ToArray(); + + UadpNetworkMessage discoveryRequestDataSetWriterConfiguration = new UadpNetworkMessage(UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration) { + DataSetWriterIds = dataSetWriterIds, + PublisherId = m_udpConnection.PubSubConnectionConfiguration.PublisherId.Value, + }; + + byte[] bytes = discoveryRequestDataSetWriterConfiguration.Encode(MessageContext); + + // send the Discovery request message to all open UADPClient + foreach (UdpClient udpClient in m_discoveryUdpClients) { - if (m_metadataWriterIdsToSend.Count == 0) + try { - // reset the interval for publisher if there is nothing to send - m_intervalRunner.Interval = kInitialRequestInterval; + Utils.Trace("UdpDiscoverySubscriber.SendDiscoveryRequestDataSetWriterConfiguration message"); + udpClient.Send(bytes, bytes.Length, DiscoveryNetworkAddressEndPoint); } + catch (Exception ex) + { + Utils.Trace(ex, "UdpDiscoverySubscriber.SendDiscoveryRequestDataSetWriterConfiguration"); + } + } - return m_metadataWriterIdsToSend.Count > 0; + // double the time between requests + m_intervalRunner.Interval = m_intervalRunner.Interval * 2; + } + + /// + /// Updates the dataset writer configuration + /// + /// the configuration + public void UpdateDataSetWriterConfiguration(WriterGroupDataType writerConfig) + { + WriterGroupDataType writerGroup = m_udpConnection.PubSubConnectionConfiguration.WriterGroups? + .Find(x => x.WriterGroupId == writerConfig.WriterGroupId); + if (writerGroup != null) + { + int index = m_udpConnection.PubSubConnectionConfiguration.WriterGroups.IndexOf(writerGroup); + m_udpConnection.PubSubConnectionConfiguration.WriterGroups[index] = writerConfig; + } + } + + /// + /// Send a discovery Request for PublisherEndpoints + /// + public void SendDiscoveryRequestPublisherEndpoints() + { + UadpNetworkMessage discoveryRequestPublisherEndpoints = new UadpNetworkMessage(UADPNetworkMessageDiscoveryType.PublisherEndpoint); + discoveryRequestPublisherEndpoints.PublisherId = m_udpConnection.PubSubConnectionConfiguration.PublisherId.Value; + + byte[] bytes = discoveryRequestPublisherEndpoints.Encode(MessageContext); + + // send the PublisherEndpoints DiscoveryRequest message to all open UdpClients + foreach (var udpClient in m_discoveryUdpClients) + { + try + { + Utils.Trace("UdpDiscoverySubscriber.SendDiscoveryRequestPublisherEndpoints message for PublisherId: {0}", + discoveryRequestPublisherEndpoints.PublisherId); + + udpClient.Send(bytes, bytes.Length, DiscoveryNetworkAddressEndPoint); + } + catch (Exception ex) + { + Utils.Trace(ex, "UdpDiscoverySubscriber.SendDiscoveryRequestPublisherEndpoints"); + } } + + // double the time between requests + m_intervalRunner.Interval *= 2; } /// /// Create and Send the DiscoveryRequest messages for DataSetMetaData /// - private void SendDiscoveryRequestDataSetMetaData() + public void SendDiscoveryRequestDataSetMetaData() { UInt16[] dataSetWriterIds = null; lock (m_lock) @@ -174,7 +236,7 @@ private void SendDiscoveryRequestDataSetMetaData() Utils.Trace("UdpDiscoverySubscriber.SendDiscoveryRequestDataSetMetaData Before sending message for DataSetWriterIds:{0}", String.Join(", ", dataSetWriterIds)); - udpClient.Send(bytes, bytes.Length, DiscoveryNetworkAddressEndPoint); + udpClient.Send(bytes, bytes.Length, DiscoveryNetworkAddressEndPoint); } catch (Exception ex) { @@ -186,5 +248,34 @@ private void SendDiscoveryRequestDataSetMetaData() m_intervalRunner.Interval = m_intervalRunner.Interval * 2; } #endregion + + #region Private Methods + /// + /// Decide if there is anything to publish + /// + /// + private bool CanPublish() + { + lock (m_lock) + { + if (m_metadataWriterIdsToSend.Count == 0) + { + // reset the interval for publisher if there is nothing to send + m_intervalRunner.Interval = kInitialRequestInterval; + } + + return m_metadataWriterIdsToSend.Count > 0; + } + } + + /// + /// Joint task to request discovery messages + /// + private void RequestDiscoveryMessages() + { + SendDiscoveryRequestDataSetMetaData(); + SendDiscoveryRequestDataSetWriterConfiguration(); + } + #endregion } } diff --git a/Libraries/Opc.Ua.PubSub/Transport/UdpPubSubConnection.cs b/Libraries/Opc.Ua.PubSub/Transport/UdpPubSubConnection.cs index 6f05c4e39..9b207ba0d 100644 --- a/Libraries/Opc.Ua.PubSub/Transport/UdpPubSubConnection.cs +++ b/Libraries/Opc.Ua.PubSub/Transport/UdpPubSubConnection.cs @@ -42,17 +42,15 @@ namespace Opc.Ua.PubSub.Transport /// /// UADP implementation of class. /// - internal class UdpPubSubConnection : UaPubSubConnection + internal class UdpPubSubConnection : UaPubSubConnection, IUadpDiscoveryMessages { #region Private Fields private List m_publisherUdpClients = new List(); private List m_subscriberUdpClients = new List(); private UdpDiscoverySubscriber m_udpDiscoverySubscriber; private UdpDiscoveryPublisher m_udpDiscoveryPublisher; - private static int s_sequenceNumber = 0; private static int s_dataSetSequenceNumber = 0; - #endregion #region Constructor @@ -71,6 +69,12 @@ public UdpPubSubConnection(UaPubSubApplication uaPubSubApplication, PubSubConnec #endregion #region Public Properties + + /// + /// Get or set the event handler + /// + public GetPublisherEndpointsEventHandler GetPublisherEndpoints { get; set; } + /// /// Get the NetworkInterface name from configured .Address. /// @@ -104,7 +108,7 @@ protected override async Task InternalStart() //publisher initialization if (Publishers.Count > 0) { - lock (m_lock) + lock (Lock) { m_publisherUdpClients = UdpClientCreator.GetUdpClients(UsedInContext.Publisher, NetworkInterfaceName, NetworkAddressEndPoint); } @@ -116,7 +120,7 @@ protected override async Task InternalStart() //subscriber initialization if (GetAllDataSetReaders().Count > 0) { - lock (m_lock) + lock (Lock) { m_subscriberUdpClients = UdpClientCreator.GetUdpClients(UsedInContext.Subscriber, NetworkInterfaceName, NetworkAddressEndPoint); @@ -139,16 +143,17 @@ protected override async Task InternalStart() await m_udpDiscoverySubscriber.StartAsync(MessageContext).ConfigureAwait(false); // add handler to metaDataReceived event - this.Application.MetaDataReceived += Application_MetaDataReceived; + this.Application.MetaDataReceived += MetaDataReceived; + this.Application.DataSetWriterConfigurationReceived += DataSetWriterConfigurationReceived; } - } + } /// /// Perform specific Stop tasks /// protected override async Task InternalStop() { - lock (m_lock) + lock (Lock) { foreach (var list in new List>() { m_publisherUdpClients, m_subscriberUdpClients }) { @@ -166,7 +171,7 @@ protected override async Task InternalStop() if (m_udpDiscoveryPublisher != null) { - await m_udpDiscoveryPublisher.StopAsync().ConfigureAwait(false); + await m_udpDiscoveryPublisher.StopAsync().ConfigureAwait(false); } if (m_udpDiscoverySubscriber != null) @@ -174,7 +179,7 @@ protected override async Task InternalStop() await m_udpDiscoverySubscriber.StopAsync().ConfigureAwait(false); // remove handler to metaDataReceived event - this.Application.MetaDataReceived -= Application_MetaDataReceived; + this.Application.MetaDataReceived -= MetaDataReceived; } } @@ -207,16 +212,16 @@ public override IList CreateNetworkMessages(WriterGroupDataTyp //check if dataSetWriter enabled if (dataSetWriter.Enabled) { - DataSet dataSet = CreateDataSet(dataSetWriter, state); + DataSet dataSet = CreateDataSet(dataSetWriter, state); if (dataSet != null) { - bool hasMetaDataChanged = state.HasMetaDataChanged(dataSetWriter, dataSet.DataSetMetaData); + bool hasMetaDataChanged = state.HasMetaDataChanged(dataSetWriter, dataSet.DataSetMetaData); if (hasMetaDataChanged) { // add metadata network message - networkMessages.Add( new UadpNetworkMessage(writerGroupConfiguration, dataSet.DataSetMetaData) { + networkMessages.Add(new UadpNetworkMessage(writerGroupConfiguration, dataSet.DataSetMetaData) { PublisherId = PubSubConnectionConfiguration.PublisherId.Value, DataSetWriterId = dataSetWriter.DataSetWriterId }); @@ -263,7 +268,7 @@ public override IList CreateNetworkMessages(WriterGroupDataTyp networkMessages.Add(uadpNetworkMessage); - + return networkMessages; } @@ -275,9 +280,9 @@ public override IList CreateNetworkMessages(WriterGroupDataTyp public IList CreateDataSetMetaDataNetworkMessages(UInt16[] dataSetWriterIds) { List networkMessages = new List(); - var writers = GetAllDataSetWriters(); + var writers = GetWriterGroupsDataType(); - foreach(UInt16 dataSetWriterId in dataSetWriterIds) + foreach (UInt16 dataSetWriterId in dataSetWriterIds) { DataSetWriterDataType writer = writers.Where(w => w.DataSetWriterId == dataSetWriterId).FirstOrDefault(); if (writer != null) @@ -294,9 +299,34 @@ public IList CreateDataSetMetaDataNetworkMessages(UInt16[] dat networkMessages.Add(networkMessage); } - } + } } - } + } + + return networkMessages; + } + + /// + /// Create and return the list of DataSetWriterConfiguration response message + /// + /// DatasetWriter ids + /// + public IList CreateDataSetWriterCofigurationMessage(UInt16[] dataSetWriterIds) + { + List networkMessages = new List(); + + IList responses = GetDataSetWriterDiscoveryResponses(dataSetWriterIds); + + foreach (DataSetWriterConfigurationResponse response in responses) + { + UadpNetworkMessage networkMessage = new UadpNetworkMessage(response.DataSetWriterIds, + response.DataSetWriterConfig, + response.StatusCodes); + + networkMessage.PublisherId = PubSubConnectionConfiguration.PublisherId.Value; + networkMessage.MessageStatusCodes.ToList().AddRange(response.StatusCodes); + networkMessages.Add(networkMessage); + } return networkMessages; } @@ -313,7 +343,7 @@ public override bool PublishNetworkMessage(UaNetworkMessage networkMessage) try { - lock (m_lock) + lock (Lock) { if (m_publisherUdpClients != null && m_publisherUdpClients.Count > 0) { @@ -354,6 +384,94 @@ public override bool AreClientsConnected() { return true; } + + #region IUadpDiscoveryMessages interface methods + + /// + /// Set GetPublisherEndpoints callback used by the subscriber to receive PublisherEndpoints data from publisher + /// + /// + public void GetPublisherEndpointsCallback(GetPublisherEndpointsEventHandler getPubliherEndpoints) + { + if (m_udpDiscoveryPublisher != null) + { + m_udpDiscoveryPublisher.GetPublisherEndpoints = getPubliherEndpoints; + } + } + + /// + /// Set GetDataSetWriterConfiguration callback used by the subscriber to receive DataSetWriter ids from publisher + /// + /// + public void GetDataSetWriterConfigurationCallback(GetDataSetWriterIdsEventHandler getDataSetWriterIds) + { + if (m_udpDiscoveryPublisher != null) + { + m_udpDiscoveryPublisher.GetDataSetWriterIds = getDataSetWriterIds; + } + } + + /// + /// Create and return the list of EndpointDescription response messages + /// To be used only by UADP Discovery response messages + /// + /// + /// + /// + /// + public UaNetworkMessage CreatePublisherEndpointsNetworkMessage(EndpointDescription[] endpoints, + StatusCode publisherProvideEndpointsStatusCode, object publisherId) + { + if (PubSubConnectionConfiguration != null && + PubSubConnectionConfiguration.TransportProfileUri == Profiles.PubSubUdpUadpTransport) + { + UadpNetworkMessage networkMessage = new UadpNetworkMessage(endpoints, publisherProvideEndpointsStatusCode); + networkMessage.PublisherId = publisherId; + + return networkMessage; + } + + return null; + } + + /// + /// Request UADP Discovery Publisher endpoints only + /// + public void RequestPublisherEndpoints() + { + if (PubSubConnectionConfiguration != null && + PubSubConnectionConfiguration.TransportProfileUri == Profiles.PubSubUdpUadpTransport && + m_udpDiscoverySubscriber != null) + { + // send discovery request publisher endpoints here for now + m_udpDiscoverySubscriber.SendDiscoveryRequestPublisherEndpoints(); + } + } + + /// + /// Request UADP Discovery DataSetWriterConfiguration messages + /// + public void RequestDataSetWriterConfiguration() + { + if (PubSubConnectionConfiguration != null && + PubSubConnectionConfiguration.TransportProfileUri == Profiles.PubSubUdpUadpTransport && + m_udpDiscoverySubscriber != null) + { + m_udpDiscoverySubscriber.SendDiscoveryRequestDataSetWriterConfiguration(); + } + } + + /// + /// Request DataSetMetaData + /// + public void RequestDataSetMetaData() + { + if (m_udpDiscoverySubscriber != null) + { + m_udpDiscoverySubscriber.SendDiscoveryRequestDataSetMetaData(); + } + } + #endregion #endregion #region Private methods @@ -378,7 +496,7 @@ private void Initialize() { Utils.Trace(Utils.TraceMasks.Error, "The configuration for connection {0} with Url:'{1}' resulted in an invalid endpoint.", PubSubConnectionConfiguration.Name, networkAddressUrlState.Url); - } + } } /// @@ -392,23 +510,23 @@ private void ProcessReceivedMessage(byte[] message, IPEndPoint source) List dataSetReaders = GetOperationalDataSetReaders(); List dataSetReadersToDecode = new List(); - + foreach (DataSetReaderDataType dataSetReader in dataSetReaders) { // check if dataSetReaders have metadata information - if (!ConfigurationVersionUtils.IsUsable(dataSetReader.DataSetMetaData)) + if (!ConfigurationVersionUtils.IsUsable(dataSetReader.DataSetMetaData)) { // check if it is possible to request the metadata information if (dataSetReader.DataSetWriterId != 0) { - m_udpDiscoverySubscriber.AddWriterIdForDataSetMetadata(dataSetReader.DataSetWriterId); + m_udpDiscoverySubscriber.AddWriterIdForDataSetMetadata(dataSetReader.DataSetWriterId); } } else { dataSetReadersToDecode.Add(dataSetReader); } - } + } UadpNetworkMessage networkMessage = new UadpNetworkMessage(); networkMessage.DataSetDecodeErrorOccurred += NetworkMessage_DataSetDecodeErrorOccurred; @@ -426,7 +544,7 @@ private void ProcessReceivedMessage(byte[] message, IPEndPoint source) /// private void OnUadpReceive(IAsyncResult result) { - lock (m_lock) + lock (Lock) { if (m_subscriberUdpClients == null || m_subscriberUdpClients.Count == 0) { @@ -451,7 +569,7 @@ private void OnUadpReceive(IAsyncResult result) if (message != null) { - Utils.Trace("OnUadpReceive received message with length {0} from {1}", message.Length, source.Address); + Utils.Trace("OnUadpReceive received message with length {0} from {1}", message.Length, source.Address); if (message.Length > 1) { @@ -495,7 +613,7 @@ private void OnUadpReceive(IAsyncResult result) { Utils.Trace(Utils.TraceMasks.Information, "OnUadpReceive BeginReceive threw Exception {0}", ex.Message); - lock (m_lock) + lock (Lock) { Renew(socket); } @@ -531,12 +649,12 @@ private void Renew(UdpClient socket) { newsocket.BeginReceive(new AsyncCallback(OnUadpReceive), newsocket); } - } + } /// /// Resets SequenceNumber /// - internal void ResetSequenceNumber() + internal static void ResetSequenceNumber() { s_sequenceNumber = 0; s_dataSetSequenceNumber = 0; @@ -547,14 +665,32 @@ internal void ResetSequenceNumber() /// /// /// - private void Application_MetaDataReceived(object sender, SubscribedDataEventArgs e) - { + private void MetaDataReceived(object sender, SubscribedDataEventArgs e) + { if (m_udpDiscoverySubscriber != null && e.NetworkMessage.DataSetWriterId != null) { m_udpDiscoverySubscriber.RemoveWriterIdForDataSetMetadata(e.NetworkMessage.DataSetWriterId.Value); } } + + /// + /// Handler for DatasetWriterConfigurationReceived event. + /// + /// + /// + private void DataSetWriterConfigurationReceived(object sender, DataSetWriterConfigurationEventArgs e) + { + lock (Lock) + { + WriterGroupDataType config = e.DataSetWriterConfiguration; + if (e.DataSetWriterConfiguration != null) + { + m_udpDiscoverySubscriber.UpdateDataSetWriterConfiguration(config); + } + } + } + /// /// Handle event. /// diff --git a/Libraries/Opc.Ua.PubSub/UaNetworkMessage.cs b/Libraries/Opc.Ua.PubSub/UaNetworkMessage.cs index cfcd63f2c..ccb0d6bd0 100644 --- a/Libraries/Opc.Ua.PubSub/UaNetworkMessage.cs +++ b/Libraries/Opc.Ua.PubSub/UaNetworkMessage.cs @@ -117,7 +117,7 @@ public UInt16? DataSetWriterId m_dataSetWriterId = (value != null) ? value.Value : (ushort)0; } } - + /// /// DataSet messages /// diff --git a/Libraries/Opc.Ua.PubSub/UaPubSubApplication.cs b/Libraries/Opc.Ua.PubSub/UaPubSubApplication.cs index e961e7615..74e8f5514 100644 --- a/Libraries/Opc.Ua.PubSub/UaPubSubApplication.cs +++ b/Libraries/Opc.Ua.PubSub/UaPubSubApplication.cs @@ -64,11 +64,22 @@ public class UaPubSubApplication : IDisposable /// public event EventHandler MetaDataReceived; + /// + /// Event that is triggered when the receives and decodes subscribed DataSet PublisherEndpoints + /// + public event EventHandler PublisherEndpointsReceived; + /// /// Event that is triggered before the configuration is updated with a new MetaData /// The configuration will not be updated if flag is set on true. /// public event EventHandler ConfigurationUpdating; + + /// + /// Event that is triggered when the receives and decodes subscribed DataSet MetaData + /// + public event EventHandler DataSetWriterConfigurationReceived; + #endregion #region Event Callbacks @@ -309,6 +320,43 @@ internal void RaiseMetaDataReceivedEvent(SubscribedDataEventArgs e) Utils.Trace(ex, "UaPubSubApplication.RaiseMetaDataReceivedEvent"); } } + /// + /// Raise DatasetWriterConfigurationReceived event + /// + /// + internal void RaiseDatasetWriterConfigurationReceivedEvent(DataSetWriterConfigurationEventArgs e) + { + try + { + if (DataSetWriterConfigurationReceived != null) + { + DataSetWriterConfigurationReceived(this, e); + } + } + catch (Exception ex) + { + Utils.Trace(ex, "UaPubSubApplication.DatasetWriterConfigurationReceivedEvent"); + } + } + + /// + /// Raise PublisherEndpointsReceived event + /// + /// + internal void RaisePublisherEndpointsReceivedEvent(PublisherEndpointsEventArgs e) + { + try + { + if (PublisherEndpointsReceived != null) + { + PublisherEndpointsReceived(this, e); + } + } + catch (Exception ex) + { + Utils.Trace(ex, "UaPubSubApplication.RaisePublisherEndpointsReceivedEvent"); + } + } /// /// Raise event diff --git a/Libraries/Opc.Ua.PubSub/UaPubSubConnection.cs b/Libraries/Opc.Ua.PubSub/UaPubSubConnection.cs index b2e235d27..af1969afd 100644 --- a/Libraries/Opc.Ua.PubSub/UaPubSubConnection.cs +++ b/Libraries/Opc.Ua.PubSub/UaPubSubConnection.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Opc.Ua.PubSub.Configuration; using Opc.Ua.PubSub.PublishedData; @@ -41,7 +42,7 @@ namespace Opc.Ua.PubSub internal abstract class UaPubSubConnection : IUaPubSubConnection { #region Fields - protected object m_lock = new object(); + protected readonly object Lock = new object(); private bool m_isRunning; private readonly List m_publishers; private readonly PubSubConnectionDataType m_pubSubConnectionDataType; @@ -56,8 +57,7 @@ internal abstract class UaPubSubConnection : IUaPubSubConnection internal UaPubSubConnection(UaPubSubApplication parentUaPubSubApplication, PubSubConnectionDataType pubSubConnectionDataType) { // set the default message context that uses the GlobalContext - MessageContext = new ServiceMessageContext - { + MessageContext = new ServiceMessageContext { NamespaceUris = ServiceMessageContext.GlobalContext.NamespaceUris, ServerUris = ServiceMessageContext.GlobalContext.ServerUris }; @@ -174,7 +174,7 @@ public void Start() InternalStart().Wait(); Utils.Trace("Connection '{0}' was started.", m_pubSubConnectionDataType.Name); - lock (m_lock) + lock (Lock) { m_isRunning = true; foreach (var publisher in m_publishers) @@ -190,7 +190,7 @@ public void Start() public void Stop() { InternalStop().Wait(); - lock (m_lock) + lock (Lock) { m_isRunning = false; foreach (var publisher in m_publishers) @@ -295,7 +295,7 @@ public List GetOperationalDataSetReaders() /// /// Processes the decoded and - /// raises the or event. + /// raises the or or or event. /// /// The network message that was received. /// The source of the received event. @@ -309,7 +309,7 @@ protected void ProcessDecodedNetworkMessage(UaNetworkMessage networkMessage, str { bool raiseChangedEvent = false; - lock (m_lock) + lock (Lock) { // check if reader's MetaData shall be updated if (reader.DataSetWriterId != 0 @@ -324,8 +324,7 @@ protected void ProcessDecodedNetworkMessage(UaNetworkMessage networkMessage, str if (raiseChangedEvent) { // raise event - ConfigurationUpdatingEventArgs metaDataUpdatedEventArgs = new ConfigurationUpdatingEventArgs() - { + ConfigurationUpdatingEventArgs metaDataUpdatedEventArgs = new ConfigurationUpdatingEventArgs() { ChangedProperty = ConfigurationProperty.DataSetMetaData, Parent = reader, NewValue = networkMessage.DataSetMetaData, @@ -341,7 +340,7 @@ protected void ProcessDecodedNetworkMessage(UaNetworkMessage networkMessage, str Utils.Trace("Connection '{0}' - The MetaData is updated for DataSetReader '{1}' with DataSetWriterId={2}", source, reader.Name, networkMessage.DataSetWriterId); - lock (m_lock) + lock (Lock) { reader.DataSetMetaData = networkMessage.DataSetMetaData; } @@ -349,8 +348,7 @@ protected void ProcessDecodedNetworkMessage(UaNetworkMessage networkMessage, str } } - SubscribedDataEventArgs subscribedDataEventArgs = new SubscribedDataEventArgs() - { + SubscribedDataEventArgs subscribedDataEventArgs = new SubscribedDataEventArgs() { NetworkMessage = networkMessage, Source = source }; @@ -365,8 +363,7 @@ protected void ProcessDecodedNetworkMessage(UaNetworkMessage networkMessage, str } else if (networkMessage.DataSetMessages != null && networkMessage.DataSetMessages.Count > 0) { - SubscribedDataEventArgs subscribedDataEventArgs = new SubscribedDataEventArgs() - { + SubscribedDataEventArgs subscribedDataEventArgs = new SubscribedDataEventArgs() { NetworkMessage = networkMessage, Source = source }; @@ -379,9 +376,50 @@ protected void ProcessDecodedNetworkMessage(UaNetworkMessage networkMessage, str source, subscribedDataEventArgs.NetworkMessage.DataSetMessages.Count); } - else + else if (networkMessage is Encoding.UadpNetworkMessage) { - Utils.Trace("Connection '{0}' - RaiseNetworkMessageDataReceivedEvent() message from source={0} cannot be decoded.", source); + Encoding.UadpNetworkMessage uadpNetworkMessage = networkMessage as Encoding.UadpNetworkMessage; + + if (uadpNetworkMessage != null) + { + if (uadpNetworkMessage.UADPDiscoveryType == UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration && + uadpNetworkMessage.UADPNetworkMessageType == UADPNetworkMessageType.DiscoveryResponse) + { + DataSetWriterConfigurationEventArgs eventArgs = new DataSetWriterConfigurationEventArgs() { + DataSetWriterIds = uadpNetworkMessage.DataSetWriterIds, + Source = source, + DataSetWriterConfiguration = uadpNetworkMessage.DataSetWriterConfiguration, + PublisherId = uadpNetworkMessage.PublisherId, + StatusCodes = uadpNetworkMessage.MessageStatusCodes + }; + + //trigger notification for received configuration + Application.RaiseDatasetWriterConfigurationReceivedEvent(eventArgs); + + Utils.Trace( + "Connection '{0}' - RaiseDataSetWriterConfigurationReceivedEvent() from source={0}, with {1} DataSetWriterConfiguration", + source, + eventArgs.DataSetWriterIds.Count()); + } + else if (uadpNetworkMessage.UADPDiscoveryType == UADPNetworkMessageDiscoveryType.PublisherEndpoint && + uadpNetworkMessage.UADPNetworkMessageType == UADPNetworkMessageType.DiscoveryResponse) + { + PublisherEndpointsEventArgs publisherEndpointsEventArgs = new PublisherEndpointsEventArgs() { + PublisherEndpoints = uadpNetworkMessage.PublisherEndpoints, + Source = source, + PublisherId = uadpNetworkMessage.PublisherId, + StatusCode = uadpNetworkMessage.PublisherProvideEndpoints + }; + + //trigger notification for received publisher endpoints + Application.RaisePublisherEndpointsReceivedEvent(publisherEndpointsEventArgs); + + Utils.Trace( + "Connection '{0}' - RaisePublisherEndpointsReceivedEvent() from source={0}, with {1} PublisherEndpoints", + source, + publisherEndpointsEventArgs.PublisherEndpoints.Length); + } + } } } @@ -404,7 +442,7 @@ protected List GetAllDataSetReaders() /// /// Get all dataset writers defined for this UaPublisher component /// - protected List GetAllDataSetWriters() + protected List GetWriterGroupsDataType() { List writerList = new List(); @@ -418,6 +456,44 @@ protected List GetAllDataSetWriters() return writerList; } + /// + /// Get data set writer discovery responses + /// + protected IList GetDataSetWriterDiscoveryResponses(UInt16[] dataSetWriterIds) + { + List responses = new List(); + + List writerGroupsIds = m_pubSubConnectionDataType.WriterGroups + .SelectMany(group => group.DataSetWriters) + .Select(writer => writer.DataSetWriterId) + .ToList(); + + foreach (var dataSetWriterId in dataSetWriterIds) + { + DataSetWriterConfigurationResponse response = new DataSetWriterConfigurationResponse(); + + if (!writerGroupsIds.Contains(dataSetWriterId)) + { + response.DataSetWriterIds = new ushort[] { dataSetWriterId }; + + response.StatusCodes = new StatusCode[] { StatusCodes.BadNotFound }; + } + else + { + response.DataSetWriterConfig = m_pubSubConnectionDataType.WriterGroups + .First(group => group.DataSetWriters.First(writer => writer.DataSetWriterId == dataSetWriterId) != null); + + response.DataSetWriterIds = new ushort[] { dataSetWriterId }; + + response.StatusCodes = new StatusCode[] { StatusCodes.Good }; + } + + responses.Add(response); + } + + return responses; + } + /// /// Get the maximum KeepAlive value from all present WriterGroups /// @@ -463,7 +539,7 @@ protected DataSet CreateDataSet(DataSetWriterDataType dataSetWriter, WriterGroup return dataSet; } - #endregion + #endregion #region Private Methods /// diff --git a/Libraries/Opc.Ua.PubSub/UaPubSubDataStore.cs b/Libraries/Opc.Ua.PubSub/UaPubSubDataStore.cs index b84e71dae..d901e4183 100644 --- a/Libraries/Opc.Ua.PubSub/UaPubSubDataStore.cs +++ b/Libraries/Opc.Ua.PubSub/UaPubSubDataStore.cs @@ -74,8 +74,7 @@ public void WritePublishedDataItem( lock (m_lock) { - var dv = new DataValue() - { + var dv = new DataValue() { WrappedValue = value, StatusCode = status ?? StatusCodes.Good, SourceTimestamp = timestamp ?? DateTime.UtcNow diff --git a/Libraries/Opc.Ua.PubSub/UaPublisher.cs b/Libraries/Opc.Ua.PubSub/UaPublisher.cs index 918bb10f7..659e6fb7e 100644 --- a/Libraries/Opc.Ua.PubSub/UaPublisher.cs +++ b/Libraries/Opc.Ua.PubSub/UaPublisher.cs @@ -155,7 +155,7 @@ private bool CanPublish() } /// - /// Generate and publish a messages + /// Generate and publish the messages /// private void PublishMessages() { @@ -174,6 +174,7 @@ private void PublishMessages() } } } + } catch (Exception e) { diff --git a/Libraries/Opc.Ua.Security.Certificates/Common/AsnUtils.cs b/Libraries/Opc.Ua.Security.Certificates/Common/AsnUtils.cs index d8bf984b1..22fe4985e 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Common/AsnUtils.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Common/AsnUtils.cs @@ -49,7 +49,7 @@ internal static string ToHexString(this byte[] buffer, bool invertEndian = false return String.Empty; } - StringBuilder builder = new StringBuilder(buffer.Length * 2); + var builder = new StringBuilder(buffer.Length * 2); if (invertEndian) { @@ -172,22 +172,22 @@ public static byte[] ParseX509Blob(byte[] blob) { try { - AsnReader x509Reader = new AsnReader(blob, AsnEncodingRules.DER); - var peekBlob = blob.AsSpan(0, x509Reader.PeekContentBytes().Length + 4).ToArray(); - var seqReader = x509Reader.ReadSequence(Asn1Tag.Sequence); + var x509Reader = new AsnReader(blob, AsnEncodingRules.DER); + byte[] peekBlob = blob.AsSpan(0, x509Reader.PeekContentBytes().Length + 4).ToArray(); + AsnReader seqReader = x509Reader.ReadSequence(Asn1Tag.Sequence); if (seqReader != null) { // Tbs encoded data - var tbs = seqReader.ReadEncodedValue(); + ReadOnlyMemory tbs = seqReader.ReadEncodedValue(); // Signature Algorithm Identifier - var sigOid = seqReader.ReadSequence(); - var signatureAlgorithm = sigOid.ReadObjectIdentifier(); - var name = Oids.GetHashAlgorithmName(signatureAlgorithm); + AsnReader sigOid = seqReader.ReadSequence(); + string signatureAlgorithm = sigOid.ReadObjectIdentifier(); + HashAlgorithmName name = Oids.GetHashAlgorithmName(signatureAlgorithm); // Signature int unusedBitCount; - var signature = seqReader.ReadBitString(out unusedBitCount); + byte[] signature = seqReader.ReadBitString(out unusedBitCount); if (unusedBitCount != 0) { throw new AsnContentException("Unexpected data in signature."); diff --git a/Libraries/Opc.Ua.Security.Certificates/Common/X509Defaults.cs b/Libraries/Opc.Ua.Security.Certificates/Common/X509Defaults.cs index 84bb86a0e..abf26ce9a 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Common/X509Defaults.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Common/X509Defaults.cs @@ -34,7 +34,7 @@ namespace Opc.Ua.Security.Certificates /// /// The defaults used in the library for Certificates. /// - public static class X509Defaults + public static class X509Defaults { /// /// The default key size for RSA certificates in bits. diff --git a/Libraries/Opc.Ua.Security.Certificates/Extensions/X509AuthorityKeyIdentifierExtension.cs b/Libraries/Opc.Ua.Security.Certificates/Extensions/X509AuthorityKeyIdentifierExtension.cs index f763b5c16..c118e6352 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Extensions/X509AuthorityKeyIdentifierExtension.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Extensions/X509AuthorityKeyIdentifierExtension.cs @@ -132,7 +132,7 @@ public X509AuthorityKeyIdentifierExtension(Oid oid, byte[] rawData, bool critica /// public override string Format(bool multiLine) { - StringBuilder buffer = new StringBuilder(); + var buffer = new StringBuilder(); if (m_keyIdentifier != null && m_keyIdentifier.Length > 0) { @@ -238,18 +238,18 @@ public override void CopyFrom(AsnEncodedData asnEncodedData) #region Private Methods private byte[] Encode() { - AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); + var writer = new AsnWriter(AsnEncodingRules.DER); writer.PushSequence(); if (m_keyIdentifier != null) { - Asn1Tag keyIdTag = new Asn1Tag(TagClass.ContextSpecific, 0); + var keyIdTag = new Asn1Tag(TagClass.ContextSpecific, 0); writer.WriteOctetString(m_keyIdentifier, keyIdTag); } if (m_issuer != null) { - Asn1Tag issuerNameTag = new Asn1Tag(TagClass.ContextSpecific, 1); + var issuerNameTag = new Asn1Tag(TagClass.ContextSpecific, 1); writer.PushSequence(issuerNameTag); // Add the issuer to constructed context-specific 4 (GeneralName.directoryName) @@ -257,7 +257,7 @@ private byte[] Encode() // X.680 2015-08 31.2.7: "The tagging construction specifies explicit tagging if any of the following holds: // ... (c) ... the type defined by "Type" is an untagged choice type, ... " // Since this is a Context-Specific tag the output is the same - Asn1Tag directoryNameTag = new Asn1Tag(TagClass.ContextSpecific, 4, true); + var directoryNameTag = new Asn1Tag(TagClass.ContextSpecific, 4, true); writer.PushSetOf(directoryNameTag); writer.WriteEncodedValue(m_issuer.RawData); writer.PopSetOf(directoryNameTag); @@ -266,8 +266,8 @@ private byte[] Encode() if (m_serialNumber != null) { - Asn1Tag issuerSerialTag = new Asn1Tag(TagClass.ContextSpecific, 2); - BigInteger issuerSerial = new BigInteger(m_serialNumber); + var issuerSerialTag = new Asn1Tag(TagClass.ContextSpecific, 2); + var issuerSerial = new BigInteger(m_serialNumber); writer.WriteInteger(issuerSerial, issuerSerialTag); } @@ -283,14 +283,14 @@ private void Decode(byte[] data) { try { - AsnReader dataReader = new AsnReader(data, AsnEncodingRules.DER); - var akiReader = dataReader.ReadSequence(); + var dataReader = new AsnReader(data, AsnEncodingRules.DER); + AsnReader akiReader = dataReader.ReadSequence(); dataReader.ThrowIfNotEmpty(); if (akiReader != null) { - Asn1Tag keyIdTag = new Asn1Tag(TagClass.ContextSpecific, 0); - Asn1Tag dnameSequencyTag = new Asn1Tag(TagClass.ContextSpecific, 1, true); - Asn1Tag serialNumberTag = new Asn1Tag(TagClass.ContextSpecific, 2); + var keyIdTag = new Asn1Tag(TagClass.ContextSpecific, 0); + var dnameSequencyTag = new Asn1Tag(TagClass.ContextSpecific, 1, true); + var serialNumberTag = new Asn1Tag(TagClass.ContextSpecific, 2); while (akiReader.HasData) { Asn1Tag peekTag = akiReader.PeekTag(); @@ -305,7 +305,7 @@ private void Decode(byte[] data) AsnReader issuerReader = akiReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 1)); if (issuerReader != null) { - Asn1Tag directoryNameTag = new Asn1Tag(TagClass.ContextSpecific, 4, true); + var directoryNameTag = new Asn1Tag(TagClass.ContextSpecific, 4, true); m_issuer = new X500DistinguishedName(issuerReader.ReadSequence(directoryNameTag).ReadEncodedValue().ToArray()); issuerReader.ThrowIfNotEmpty(); } diff --git a/Libraries/Opc.Ua.Security.Certificates/Extensions/X509CrlNumberExtension.cs b/Libraries/Opc.Ua.Security.Certificates/Extensions/X509CrlNumberExtension.cs index 7f97e9985..2f6c0a063 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Extensions/X509CrlNumberExtension.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Extensions/X509CrlNumberExtension.cs @@ -97,7 +97,7 @@ public X509CrlNumberExtension(BigInteger crlNumber) /// public override string Format(bool multiLine) { - StringBuilder buffer = new StringBuilder(); + var buffer = new StringBuilder(); buffer.Append(kFriendlyName); buffer.Append('='); buffer.Append(CrlNumber); @@ -136,7 +136,7 @@ public override void CopyFrom(AsnEncodedData asnEncodedData) /// private byte[] Encode() { - AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); + var writer = new AsnWriter(AsnEncodingRules.DER); writer.WriteInteger(CrlNumber); return writer.Encode(); } @@ -150,7 +150,7 @@ private void Decode(byte[] data) { try { - AsnReader dataReader = new AsnReader(data, AsnEncodingRules.DER); + var dataReader = new AsnReader(data, AsnEncodingRules.DER); CrlNumber = dataReader.ReadInteger(); dataReader.ThrowIfNotEmpty(); } diff --git a/Libraries/Opc.Ua.Security.Certificates/Extensions/X509Extensions.cs b/Libraries/Opc.Ua.Security.Certificates/Extensions/X509Extensions.cs index 0010ef52b..c4ac193c7 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Extensions/X509Extensions.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Extensions/X509Extensions.cs @@ -65,7 +65,7 @@ public static T FindExtension(this X509ExtensionCollection extensions) where // search known custom extensions if (typeof(T) == typeof(X509AuthorityKeyIdentifierExtension)) { - var extension = extensions.Cast().FirstOrDefault(e => ( + X509Extension extension = extensions.Cast().FirstOrDefault(e => ( e.Oid.Value == X509AuthorityKeyIdentifierExtension.AuthorityKeyIdentifierOid || e.Oid.Value == X509AuthorityKeyIdentifierExtension.AuthorityKeyIdentifier2Oid) ); @@ -77,7 +77,7 @@ public static T FindExtension(this X509ExtensionCollection extensions) where if (typeof(T) == typeof(X509SubjectAltNameExtension)) { - var extension = extensions.Cast().FirstOrDefault(e => ( + X509Extension extension = extensions.Cast().FirstOrDefault(e => ( e.Oid.Value == X509SubjectAltNameExtension.SubjectAltNameOid || e.Oid.Value == X509SubjectAltNameExtension.SubjectAltName2Oid) ); @@ -89,7 +89,7 @@ public static T FindExtension(this X509ExtensionCollection extensions) where if (typeof(T) == typeof(X509CrlNumberExtension)) { - var extension = extensions.Cast().FirstOrDefault(e => ( + X509Extension extension = extensions.Cast().FirstOrDefault(e => ( e.Oid.Value == X509CrlNumberExtension.CrlNumberOid) ); if (extension != null) @@ -119,12 +119,12 @@ public static X509Extension BuildX509AuthorityInformationAccess( throw new ArgumentNullException(nameof(caIssuerUrls), "One CA Issuer Url or OCSP responder is required for the extension."); } - Asn1Tag generalNameUriChoice = new Asn1Tag(TagClass.ContextSpecific, 6); - AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); + var generalNameUriChoice = new Asn1Tag(TagClass.ContextSpecific, 6); + var writer = new AsnWriter(AsnEncodingRules.DER); writer.PushSequence(); if (caIssuerUrls != null) { - foreach (var caIssuerUrl in caIssuerUrls) + foreach (string caIssuerUrl in caIssuerUrls) { writer.PushSequence(); writer.WriteObjectIdentifier(Oids.CertificateAuthorityIssuers); @@ -171,8 +171,8 @@ public static X509Extension BuildX509CRLDistributionPoints( var context0 = new Asn1Tag(TagClass.ContextSpecific, 0, true); Asn1Tag distributionPointChoice = context0; Asn1Tag fullNameChoice = context0; - Asn1Tag generalNameUriChoice = new Asn1Tag(TagClass.ContextSpecific, 6); - AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); + var generalNameUriChoice = new Asn1Tag(TagClass.ContextSpecific, 6); + var writer = new AsnWriter(AsnEncodingRules.DER); writer.PushSequence(); writer.PushSequence(); writer.PushSequence(distributionPointChoice); @@ -201,15 +201,15 @@ public static X509Extension ReadExtension(this AsnReader reader) if (reader.HasData) { var boolTag = new Asn1Tag(UniversalTagNumber.Boolean); - var extReader = reader.ReadSequence(); - var extOid = extReader.ReadObjectIdentifier(); + AsnReader extReader = reader.ReadSequence(); + string extOid = extReader.ReadObjectIdentifier(); bool critical = false; - var peekTag = extReader.PeekTag(); + Asn1Tag peekTag = extReader.PeekTag(); if (peekTag == boolTag) { critical = extReader.ReadBoolean(); } - var data = extReader.ReadOctetString(); + byte[] data = extReader.ReadOctetString(); extReader.ThrowIfNotEmpty(); return new X509Extension(new Oid(extOid), data, critical); } @@ -223,7 +223,7 @@ public static X509Extension ReadExtension(this AsnReader reader) /// public static void WriteExtension(this AsnWriter writer, X509Extension extension) { - var etag = Asn1Tag.Sequence; + Asn1Tag etag = Asn1Tag.Sequence; writer.PushSequence(etag); writer.WriteObjectIdentifier(extension.Oid.Value); if (extension.Critical) @@ -241,7 +241,7 @@ public static X509Extension BuildX509CRLReason( CRLReason reason ) { - AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); + var writer = new AsnWriter(AsnEncodingRules.DER); writer.WriteEnumeratedValue(reason); return new X509Extension(Oids.CrlReasonCode, writer.Encode(), false); } @@ -253,7 +253,7 @@ CRLReason reason public static X509Extension BuildAuthorityKeyIdentifier(X509Certificate2 issuerCaCertificate) { // force exception if SKI is not present - var ski = issuerCaCertificate.Extensions.OfType().Single(); + X509SubjectKeyIdentifierExtension ski = issuerCaCertificate.Extensions.OfType().Single(); return new X509AuthorityKeyIdentifierExtension( ski.SubjectKeyIdentifier.FromHexString(), issuerCaCertificate.IssuerName, @@ -265,7 +265,7 @@ public static X509Extension BuildAuthorityKeyIdentifier(X509Certificate2 issuerC /// public static X509Extension BuildCRLNumber(BigInteger crlNumber) { - AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); + var writer = new AsnWriter(AsnEncodingRules.DER); writer.WriteInteger(crlNumber); return new X509Extension(Oids.CrlNumber, writer.Encode(), false); } diff --git a/Libraries/Opc.Ua.Security.Certificates/Extensions/X509SubjectAltNameExtension.cs b/Libraries/Opc.Ua.Security.Certificates/Extensions/X509SubjectAltNameExtension.cs index 700a07264..d799c1871 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Extensions/X509SubjectAltNameExtension.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Extensions/X509SubjectAltNameExtension.cs @@ -131,7 +131,7 @@ public X509SubjectAltNameExtension( public override string Format(bool multiLine) { EnsureDecoded(); - StringBuilder buffer = new StringBuilder(); + var buffer = new StringBuilder(); for (int ii = 0; ii < m_uris.Count; ii++) { if (buffer.Length > 0) @@ -259,11 +259,11 @@ public IReadOnlyList IPAddresses /// /// Create a normalized IPv4 or IPv6 address from a 4 byte or 16 byte array. /// - private string IPAddressToString(byte[] encodedIPAddress) + private static string IPAddressToString(byte[] encodedIPAddress) { try { - IPAddress address = new IPAddress(encodedIPAddress); + var address = new IPAddress(encodedIPAddress); return address.ToString(); } catch @@ -279,13 +279,13 @@ private string IPAddressToString(byte[] encodedIPAddress) private byte[] Encode() { var sanBuilder = new SubjectAlternativeNameBuilder(); - foreach (var uri in m_uris) + foreach (string uri in m_uris) { sanBuilder.AddUri(new Uri(uri)); } - EncodeGeneralNames(sanBuilder, m_domainNames); - EncodeGeneralNames(sanBuilder, m_ipAddresses); - var extension = sanBuilder.Build(); + X509SubjectAltNameExtension.EncodeGeneralNames(sanBuilder, m_domainNames); + X509SubjectAltNameExtension.EncodeGeneralNames(sanBuilder, m_ipAddresses); + X509Extension extension = sanBuilder.Build(); return extension.RawData; } @@ -294,7 +294,7 @@ private byte[] Encode() /// /// The subject alternative name builder /// The general Names to add - private void EncodeGeneralNames(SubjectAlternativeNameBuilder sanBuilder, IList generalNames) + private static void EncodeGeneralNames(SubjectAlternativeNameBuilder sanBuilder, IList generalNames) { foreach (string generalName in generalNames) { @@ -347,35 +347,35 @@ private void Decode(byte[] data) { try { - List uris = new List(); - List domainNames = new List(); - List ipAddresses = new List(); - AsnReader dataReader = new AsnReader(data, AsnEncodingRules.DER); - var akiReader = dataReader.ReadSequence(); + var uris = new List(); + var domainNames = new List(); + var ipAddresses = new List(); + var dataReader = new AsnReader(data, AsnEncodingRules.DER); + AsnReader akiReader = dataReader.ReadSequence(); dataReader.ThrowIfNotEmpty(); if (akiReader != null) { - Asn1Tag uriTag = new Asn1Tag(TagClass.ContextSpecific, 6); - Asn1Tag dnsTag = new Asn1Tag(TagClass.ContextSpecific, 2); - Asn1Tag ipTag = new Asn1Tag(TagClass.ContextSpecific, 7); + var uriTag = new Asn1Tag(TagClass.ContextSpecific, 6); + var dnsTag = new Asn1Tag(TagClass.ContextSpecific, 2); + var ipTag = new Asn1Tag(TagClass.ContextSpecific, 7); while (akiReader.HasData) { Asn1Tag peekTag = akiReader.PeekTag(); if (peekTag == uriTag) { - var uri = akiReader.ReadCharacterString(UniversalTagNumber.IA5String, uriTag); + string uri = akiReader.ReadCharacterString(UniversalTagNumber.IA5String, uriTag); uris.Add(uri); } else if (peekTag == dnsTag) { - var dnsName = akiReader.ReadCharacterString(UniversalTagNumber.IA5String, dnsTag); + string dnsName = akiReader.ReadCharacterString(UniversalTagNumber.IA5String, dnsTag); domainNames.Add(dnsName); } else if (peekTag == ipTag) { - var ip = akiReader.ReadOctetString(ipTag); - ipAddresses.Add(IPAddressToString(ip)); + byte[] ip = akiReader.ReadOctetString(ipTag); + ipAddresses.Add(X509SubjectAltNameExtension.IPAddressToString(ip)); } else // skip over { @@ -406,9 +406,9 @@ private void Decode(byte[] data) /// The general names. DNS Hostnames, IPv4 or IPv6 addresses private void Initialize(string applicationUri, IEnumerable generalNames) { - List uris = new List(); - List domainNames = new List(); - List ipAddresses = new List(); + var uris = new List(); + var domainNames = new List(); + var ipAddresses = new List(); uris.Add(applicationUri); foreach (string generalName in generalNames) { @@ -419,6 +419,8 @@ private void Initialize(string applicationUri, IEnumerable generalNames) case UriHostNameType.IPv4: case UriHostNameType.IPv6: ipAddresses.Add(generalName); break; + case UriHostNameType.Unknown: + case UriHostNameType.Basic: default: continue; } } diff --git a/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj b/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj index 41a3bbf1a..6466990df 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj +++ b/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj @@ -15,26 +15,27 @@ true - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - + + + + + + + + + + + + + + $(DefineConstants);ECC_SUPPORT + + + + + + - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - $(PackageId).Debug @@ -44,23 +45,15 @@ - - - - - + - + - - - - - + diff --git a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/CertificateBuilder.cs b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/CertificateBuilder.cs index 3ceb7facb..9d4ea2727 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/CertificateBuilder.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/CertificateBuilder.cs @@ -144,8 +144,8 @@ public static byte[] CreatePfxWithRSAPrivateKey( RSA privateKey, string passcode) { - var x509 = new X509CertificateParser().ReadCertificate(certificate.RawData); - using (var cfrg = new CertificateFactoryRandomGenerator()) + Org.BouncyCastle.X509.X509Certificate x509 = new X509CertificateParser().ReadCertificate(certificate.RawData); + using (var cfrg = new CryptoApiRandomGenerator()) { return X509Utils.CreatePfxWithPrivateKey( x509, friendlyName, @@ -165,7 +165,7 @@ public static byte[] CreateSigningRequest( ) { if (certificate == null) throw new ArgumentNullException(nameof(certificate)); - using (var cfrg = new CertificateFactoryRandomGenerator()) + using (var cfrg = new CryptoApiRandomGenerator()) { SecureRandom random = new SecureRandom(cfrg); @@ -177,7 +177,7 @@ public static byte[] CreateSigningRequest( new Asn1SignatureFactory(X509Utils.GetRSAHashAlgorithm(X509Defaults.HashAlgorithmName), signingKey, random); Asn1Set attributes = null; - var san = X509Extensions.FindExtension(certificate); + X509SubjectAltNameExtension san = X509Extensions.FindExtension(certificate); X509SubjectAltNameExtension alternateName = new X509SubjectAltNameExtension(san, san.Critical); string applicationUri = null; @@ -188,14 +188,14 @@ public static byte[] CreateSigningRequest( { applicationUri = alternateName.Uris[0]; } - foreach (var name in alternateName.DomainNames) + foreach (string name in alternateName.DomainNames) { if (!domainNames.Any(s => s.Equals(name, StringComparison.OrdinalIgnoreCase))) { domainNames.Add(name); } } - foreach (var ipAddress in alternateName.IPAddresses) + foreach (string ipAddress in alternateName.IPAddresses) { if (!domainNames.Any(s => s.Equals(ipAddress, StringComparison.OrdinalIgnoreCase))) { @@ -219,8 +219,9 @@ public static byte[] CreateSigningRequest( if (generalNames.Count > 0) { - IList oids = new ArrayList(); - IList values = new ArrayList(); + IList oids = new List(); + IList values + = new List(); oids.Add(Org.BouncyCastle.Asn1.X509.X509Extensions.SubjectAlternativeName); values.Add(new Org.BouncyCastle.Asn1.X509.X509Extension(false, new DerOctetString(new GeneralNames(generalNames.ToArray()).GetDerEncoded()))); @@ -345,7 +346,7 @@ private void CreateExtensions(X509V3CertificateGenerator cg, AsymmetricKeyParame if (!m_isCA) { // Key usage - var keyUsage = KeyUsage.DataEncipherment | KeyUsage.DigitalSignature | + int keyUsage = KeyUsage.DataEncipherment | KeyUsage.DigitalSignature | KeyUsage.NonRepudiation | KeyUsage.KeyEncipherment; if (IssuerCAKeyCert == null) { // only self signed certs need KeyCertSign flag. @@ -378,7 +379,7 @@ private void CreateExtensions(X509V3CertificateGenerator cg, AsymmetricKeyParame } } - foreach (var extension in m_extensions) + foreach (System.Security.Cryptography.X509Certificates.X509Extension extension in m_extensions) { cg.AddExtension(extension.Oid.Value, extension.Critical, Asn1Object.FromByteArray(extension.RawData)); } @@ -443,7 +444,7 @@ private byte[] CreatePfxForRSA(string passcode, ISignatureFactory signatureFacto throw new NotSupportedException("Need an issuer certificate with a private key or a signature generator."); } - using (var cfrg = new CertificateFactoryRandomGenerator()) + using (var cfrg = new CryptoApiRandomGenerator()) { // cert generators SecureRandom random = new SecureRandom(cfrg); diff --git a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/CertificateFactoryRandomGenerator.cs b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/CertificateFactoryRandomGenerator.cs deleted file mode 100644 index 03fb62d8d..000000000 --- a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/CertificateFactoryRandomGenerator.cs +++ /dev/null @@ -1,92 +0,0 @@ -/* ======================================================================== - * Copyright (c) 2005-2021 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/ - * ======================================================================*/ - -#if !NETSTANDARD2_1 && !NET472_OR_GREATER && !NET5_0_OR_GREATER -using System; -using System.Security.Cryptography; -using Org.BouncyCastle.Crypto.Prng; - -namespace Opc.Ua.Security.Certificates.BouncyCastle -{ - /// - /// Secure .Net Core Random Number generator wrapper for Bounce Castle. - /// Creates an instance of RNGCryptoServiceProvider or an OpenSSL based version on other OS. - /// - public class CertificateFactoryRandomGenerator : IRandomGenerator, IDisposable - { - RandomNumberGenerator m_prg; - - /// - /// Creates an instance of a crypthographic secure random number generator. - /// - public CertificateFactoryRandomGenerator() - { - m_prg = RandomNumberGenerator.Create(); - } - - /// - /// Dispose the random number generator. - /// - public void Dispose() - { - m_prg.Dispose(); - } - - /// Add more seed material to the generator. Not needed here. - public void AddSeedMaterial(byte[] seed) { } - - /// Add more seed material to the generator. Not needed here. - public void AddSeedMaterial(long seed) { } - - /// - /// Fills an array of bytes with a cryptographically strong - /// random sequence of values. - /// - /// Array to be filled. - public void NextBytes(byte[] bytes) - { - m_prg.GetBytes(bytes); - } - - /// - /// Fills an array of bytes with a cryptographically strong - /// random sequence of values. - /// - /// Array to receive bytes. - /// Index to start filling at. - /// Length of segment to fill. - public void NextBytes(byte[] bytes, int start, int len) - { - byte[] temp = new byte[len]; - m_prg.GetBytes(temp); - Array.Copy(temp, 0, bytes, start, len); - } - } -} -#endif diff --git a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/PEMReader.cs b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/PEMReader.cs index 3a9d37608..6ad498e60 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/PEMReader.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/PEMReader.cs @@ -53,7 +53,7 @@ public static RSA ImportPrivateKeyFromPEM( { RSA rsaPrivateKey = null; Org.BouncyCastle.OpenSsl.PemReader pemReader; - using (StreamReader pemStreamReader = new StreamReader(new MemoryStream(pemDataBlob), Encoding.UTF8, true)) + using (var pemStreamReader = new StreamReader(new MemoryStream(pemDataBlob), Encoding.UTF8, true)) { if (String.IsNullOrEmpty(password)) { @@ -61,18 +61,17 @@ public static RSA ImportPrivateKeyFromPEM( } else { - Password pwFinder = new Password(password.ToCharArray()); + var pwFinder = new Password(password.ToCharArray()); pemReader = new Org.BouncyCastle.OpenSsl.PemReader(pemStreamReader, pwFinder); } try { // find the private key in the PEM blob - var pemObject = pemReader.ReadObject(); + object pemObject = pemReader.ReadObject(); while (pemObject != null) { RsaPrivateCrtKeyParameters privateKey = null; - var keypair = pemObject as Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair; - if (keypair != null) + if (pemObject is Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair keypair) { privateKey = keypair.Private as RsaPrivateCrtKeyParameters; } @@ -115,17 +114,17 @@ public static RSA ImportPrivateKeyFromPEM( internal class Password : IPasswordFinder { - private readonly char[] password; + private readonly char[] m_password; public Password( char[] word) { - this.password = (char[])word.Clone(); + this.m_password = (char[])word.Clone(); } public char[] GetPassword() { - return (char[])password.Clone(); + return (char[])m_password.Clone(); } } #endregion diff --git a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Extensions.cs b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Extensions.cs index af74e15fb..7741a406c 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Extensions.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Extensions.cs @@ -49,13 +49,13 @@ public static X509Extension BuildSubjectAltNameExtension(IList uris, ILi { // subject alternate name var generalNames = new List(); - foreach (var uri in uris) + foreach (string uri in uris) { generalNames.Add(new GeneralName(GeneralName.UniformResourceIdentifier, uri)); } generalNames.AddRange(CreateSubjectAlternateNameDomains(domainNames)); generalNames.AddRange(CreateSubjectAlternateNameDomains(ipAddresses)); - var rawData = new DerOctetString(new GeneralNames(generalNames.ToArray()).GetDerEncoded()).GetOctets(); + byte[] rawData = new DerOctetString(new GeneralNames(generalNames.ToArray()).GetDerEncoded()).GetOctets(); return new X509Extension(Org.BouncyCastle.Asn1.X509.X509Extensions.SubjectAlternativeName.Id, rawData, false); } diff --git a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509SignatureFactory.cs b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509SignatureFactory.cs index 9926427fd..131cc07d2 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509SignatureFactory.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509SignatureFactory.cs @@ -41,9 +41,9 @@ namespace Opc.Ua.Security.Certificates.BouncyCastle /// public class X509SignatureFactory : ISignatureFactory { - private readonly AlgorithmIdentifier _algID; - private readonly HashAlgorithmName _hashAlgorithm; - private readonly X509SignatureGenerator _generator; + private readonly AlgorithmIdentifier m_algID; + private readonly HashAlgorithmName m_hashAlgorithm; + private readonly X509SignatureGenerator m_generator; /// /// Constructor which also specifies a source of randomness to be used if one is required. @@ -73,24 +73,24 @@ public X509SignatureFactory(HashAlgorithmName hashAlgorithm, X509SignatureGenera { throw new ArgumentOutOfRangeException(nameof(hashAlgorithm)); } - _hashAlgorithm = hashAlgorithm; - _generator = generator; - _algID = new AlgorithmIdentifier(sigOid); + m_hashAlgorithm = hashAlgorithm; + m_generator = generator; + m_algID = new AlgorithmIdentifier(sigOid); } /// - public Object AlgorithmDetails => _algID; + public Object AlgorithmDetails => m_algID; /// - public IStreamCalculator CreateCalculator() + public IStreamCalculator CreateCalculator() { - return new X509StreamCalculator(_generator, _hashAlgorithm); + return new X509StreamCalculator(m_generator, m_hashAlgorithm); } /// /// Signs a Bouncy Castle digest stream with the .Net X509SignatureGenerator. /// - class X509StreamCalculator : IStreamCalculator + class X509StreamCalculator : IStreamCalculator { private X509SignatureGenerator _generator; private readonly HashAlgorithmName _hashAlgorithm; @@ -117,36 +117,12 @@ public X509StreamCalculator( /// /// Callback signs the digest with X509SignatureGenerator. /// - public object GetResult() + public IBlockResult GetResult() { - var memStream = Stream as MemoryStream; - if (memStream == null) throw new ArgumentNullException(nameof(Stream)); - var digest = memStream.ToArray(); - var signature = _generator.SignData(digest, _hashAlgorithm); - return new MemoryBlockResult(signature); - } - } - - /// - /// Helper for Bouncy Castle signing operation to store the result in a memory block. - /// - class MemoryBlockResult : IBlockResult - { - private readonly byte[] _data; - /// - public MemoryBlockResult(byte[] data) - { - _data = data; - } - /// - public byte[] Collect() - { - return _data; - } - /// - public int Collect(byte[] destination, int offset) - { - throw new NotImplementedException(); + if (!(Stream is MemoryStream memStream)) throw new ArgumentNullException(nameof(Stream)); + byte[] digest = memStream.ToArray(); + byte[] signature = _generator.SignData(digest, _hashAlgorithm); + return new Org.BouncyCastle.Crypto.SimpleBlockResult(signature); } } } diff --git a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Utils.cs b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Utils.cs index ae896e0e7..6e6495c29 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Utils.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Utils.cs @@ -60,12 +60,12 @@ internal static byte[] CreatePfxWithPrivateKey( SecureRandom random) { // create pkcs12 store for cert and private key - using (MemoryStream pfxData = new MemoryStream()) + using (var pfxData = new MemoryStream()) { - Pkcs12StoreBuilder builder = new Pkcs12StoreBuilder(); + var builder = new Pkcs12StoreBuilder(); builder.SetUseDerEncoding(true); Pkcs12Store pkcsStore = builder.Build(); - X509CertificateEntry[] chain = new X509CertificateEntry[1]; + var chain = new X509CertificateEntry[1]; chain[0] = new X509CertificateEntry(certificate); if (string.IsNullOrEmpty(friendlyName)) { @@ -170,7 +170,7 @@ internal static BigInteger GetSerialNumber(X509Certificate2 certificate) /// internal static string GetCertificateCommonName(Org.BouncyCastle.X509.X509Certificate certificate) { - var subjectDN = certificate.SubjectDN.GetValueList(X509Name.CN); + System.Collections.Generic.IList subjectDN = certificate.SubjectDN.GetValueList(X509Name.CN); if (subjectDN.Count > 0) { return subjectDN[0].ToString(); @@ -184,7 +184,7 @@ internal static string GetCertificateCommonName(Org.BouncyCastle.X509.X509Certif internal static string GeneratePasscode() { const int kLength = 18; - using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + using (var rng = RandomNumberGenerator.Create()) { byte[] tokenBuffer = new byte[kLength]; rng.GetBytes(tokenBuffer); @@ -197,13 +197,13 @@ internal static string GeneratePasscode() /// internal static RSA SetRSAPublicKey(byte[] publicKey) { - var asymmetricKeyParameter = PublicKeyFactory.CreateKey(publicKey); + AsymmetricKeyParameter asymmetricKeyParameter = PublicKeyFactory.CreateKey(publicKey); var rsaKeyParameters = asymmetricKeyParameter as RsaKeyParameters; var parameters = new RSAParameters { Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned(), Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned() }; - RSA rsaPublicKey = RSA.Create(); + var rsaPublicKey = RSA.Create(); rsaPublicKey.ImportParameters(parameters); return rsaPublicKey; } diff --git a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/CertificateBuilder.cs b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/CertificateBuilder.cs index ac50044d0..10c5908a0 100644 --- a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/CertificateBuilder.cs +++ b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/CertificateBuilder.cs @@ -30,7 +30,6 @@ #if NETSTANDARD2_1 || NET472_OR_GREATER || NET5_0_OR_GREATER using System; -using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; @@ -96,13 +95,13 @@ public override X509Certificate2 CreateForRSA() rsaPublicKey = rsaKeyPair; } - var padding = RSASignaturePadding.Pkcs1; + RSASignaturePadding padding = RSASignaturePadding.Pkcs1; var request = new CertificateRequest(SubjectName, rsaPublicKey, HashAlgorithmName, padding); CreateX509Extensions(request, false); X509Certificate2 signedCert; - var serialNumber = m_serialNumber.Reverse().ToArray(); + byte[] serialNumber = m_serialNumber.Reverse().ToArray(); if (IssuerCAKeyCert != null) { using (RSA rsaIssuerKey = IssuerCAKeyCert.GetRSAPrivateKey()) @@ -140,7 +139,7 @@ public override X509Certificate2 CreateForRSA(X509SignatureGenerator generator) throw new NotSupportedException("Need an issuer certificate or a public key for a signature generator."); } - var issuerSubjectName = SubjectName; + X500DistinguishedName issuerSubjectName = SubjectName; if (IssuerCAKeyCert != null) { issuerSubjectName = IssuerCAKeyCert.SubjectName; @@ -197,7 +196,7 @@ public override X509Certificate2 CreateForECDsa() CreateX509Extensions(request, true); - var serialNumber = m_serialNumber.Reverse().ToArray(); + byte[] serialNumber = m_serialNumber.Reverse().ToArray(); if (IssuerCAKeyCert != null) { using (ECDsa issuerKey = IssuerCAKeyCert.GetECDsaPrivateKey()) @@ -418,7 +417,7 @@ private void CreateX509Extensions(CertificateRequest request, bool forECDsa) } } - foreach (var extension in m_extensions) + foreach (X509Extension extension in m_extensions) { request.CertificateExtensions.Add(extension); } diff --git a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509PfxUtils.cs b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509PfxUtils.cs index 4a5706cc9..6b0c2e1a9 100644 --- a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509PfxUtils.cs +++ b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509PfxUtils.cs @@ -49,7 +49,7 @@ public static class X509PfxUtils /// private static X509KeyUsageFlags GetKeyUsage(X509Certificate2 cert) { - var allFlags = X509KeyUsageFlags.None; + X509KeyUsageFlags allFlags = X509KeyUsageFlags.None; foreach (X509KeyUsageExtension ext in cert.Extensions.OfType()) { allFlags |= ext.KeyUsages; @@ -133,7 +133,7 @@ string password }; // try some combinations of storage flags, support is platform dependent - foreach (var flag in storageFlags) + foreach (X509KeyStorageFlags flag in storageFlags) { try { @@ -193,8 +193,8 @@ internal static bool VerifyRSAKeyPairSign( byte[] testBlock = new byte[TestBlockSize]; var rnd = new Random(); rnd.NextBytes(testBlock); - byte[] signature = rsaPrivateKey.SignData(testBlock, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); - return rsaPublicKey.VerifyData(testBlock, signature, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); + byte[] signature = rsaPrivateKey.SignData(testBlock, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + return rsaPublicKey.VerifyData(testBlock, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); } #if ECC_SUPPORT diff --git a/Libraries/Opc.Ua.Security.Certificates/X509Crl/CrlBuilder.cs b/Libraries/Opc.Ua.Security.Certificates/X509Crl/CrlBuilder.cs index 44310b2d9..b96294cc1 100644 --- a/Libraries/Opc.Ua.Security.Certificates/X509Crl/CrlBuilder.cs +++ b/Libraries/Opc.Ua.Security.Certificates/X509Crl/CrlBuilder.cs @@ -84,7 +84,7 @@ private CrlBuilder(IX509CRL crl) RawData = crl.RawData; m_revokedCertificates = new List(crl.RevokedCertificates); m_crlExtensions = new X509ExtensionCollection(); - foreach (var extension in crl.CrlExtensions) + foreach (X509Extension extension in crl.CrlExtensions) { m_crlExtensions.Add(extension); } @@ -237,8 +237,8 @@ public CrlBuilder AddCRLExtension(X509Extension extension) /// The signed CRL. public IX509CRL CreateSignature(X509SignatureGenerator generator) { - var tbsRawData = Encode(); - var signatureAlgorithm = generator.GetSignatureAlgorithmIdentifier(HashAlgorithmName); + byte[] tbsRawData = Encode(); + byte[] signatureAlgorithm = generator.GetSignatureAlgorithmIdentifier(HashAlgorithmName); byte[] signature = generator.SignData(tbsRawData, HashAlgorithmName); var crlSigner = new X509Signature(tbsRawData, signature, signatureAlgorithm); RawData = crlSigner.Encode(); @@ -306,7 +306,7 @@ public IX509CRL CreateForECDsa(X509Certificate2 issuerCertificate) /// internal byte[] Encode() { - AsnWriter crlWriter = new AsnWriter(AsnEncodingRules.DER); + var crlWriter = new AsnWriter(AsnEncodingRules.DER); { // tbsCertList crlWriter.PushSequence(); @@ -339,18 +339,18 @@ internal byte[] Encode() // sequence to start the revoked certificates. crlWriter.PushSequence(); - foreach (var revokedCert in RevokedCertificates) + foreach (RevokedCertificate revokedCert in RevokedCertificates) { crlWriter.PushSequence(); - BigInteger srlNumberValue = new BigInteger(revokedCert.UserCertificate); + var srlNumberValue = new BigInteger(revokedCert.UserCertificate); crlWriter.WriteInteger(srlNumberValue); WriteTime(crlWriter, revokedCert.RevocationDate); if (revokedCert.CrlEntryExtensions.Count > 0) { crlWriter.PushSequence(); - foreach (var crlEntryExt in revokedCert.CrlEntryExtensions) + foreach (X509Extension crlEntryExt in revokedCert.CrlEntryExtensions) { crlWriter.WriteExtension(crlEntryExt); } @@ -370,7 +370,7 @@ internal byte[] Encode() // CRL extensions crlWriter.PushSequence(); - foreach (var extension in CrlExtensions) + foreach (X509Extension extension in CrlExtensions) { crlWriter.WriteExtension(extension); } diff --git a/Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Crl.cs b/Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Crl.cs index 563fbf2c4..5e76dc4ff 100644 --- a/Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Crl.cs +++ b/Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Crl.cs @@ -73,7 +73,7 @@ public X509CRL(IX509CRL crl) m_nextUpdate = crl.NextUpdate; m_revokedCertificates = new List(crl.RevokedCertificates); m_crlExtensions = new X509ExtensionCollection(); - foreach (var extension in crl.CrlExtensions) + foreach (X509Extension extension in crl.CrlExtensions) { m_crlExtensions.Add(extension); } @@ -194,8 +194,8 @@ public bool IsRevoked(X509Certificate2 certificate) throw new CryptographicException("Certificate was not created by the CRL Issuer."); } EnsureDecoded(); - var serialnumber = certificate.GetSerialNumber(); - foreach (var revokedCert in RevokedCertificates) + byte[] serialnumber = certificate.GetSerialNumber(); + foreach (RevokedCertificate revokedCert in RevokedCertificates) { if (serialnumber.SequenceEqual(revokedCert.UserCertificate)) { @@ -227,16 +227,16 @@ internal void DecodeCrl(byte[] tbs) { try { - AsnReader crlReader = new AsnReader(tbs, AsnEncodingRules.DER); - var tag = Asn1Tag.Sequence; - var seqReader = crlReader.ReadSequence(tag); + var crlReader = new AsnReader(tbs, AsnEncodingRules.DER); + Asn1Tag tag = Asn1Tag.Sequence; + AsnReader seqReader = crlReader.ReadSequence(tag); crlReader.ThrowIfNotEmpty(); if (seqReader != null) { // Version is OPTIONAL uint version = 0; var intTag = new Asn1Tag(UniversalTagNumber.Integer); - var peekTag = seqReader.PeekTag(); + Asn1Tag peekTag = seqReader.PeekTag(); if (peekTag == intTag) { if (seqReader.TryReadUInt32(out version)) @@ -249,8 +249,8 @@ internal void DecodeCrl(byte[] tbs) } // Signature Algorithm Identifier - var sigReader = seqReader.ReadSequence(); - var oid = sigReader.ReadObjectIdentifier(); + AsnReader sigReader = seqReader.ReadSequence(); + string oid = sigReader.ReadObjectIdentifier(); m_hashAlgorithmName = Oids.GetHashAlgorithmName(oid); if (sigReader.HasData) { @@ -262,32 +262,32 @@ internal void DecodeCrl(byte[] tbs) m_issuerName = new X500DistinguishedName(seqReader.ReadEncodedValue().ToArray()); // thisUpdate - m_thisUpdate = ReadTime(seqReader, optional: false); + m_thisUpdate = X509CRL.ReadTime(seqReader, optional: false); // nextUpdate is OPTIONAL - m_nextUpdate = ReadTime(seqReader, optional: true); + m_nextUpdate = X509CRL.ReadTime(seqReader, optional: true); var seqTag = new Asn1Tag(UniversalTagNumber.Sequence, true); peekTag = seqReader.PeekTag(); if (peekTag == seqTag) { // revoked certificates - var revReader = seqReader.ReadSequence(tag); + AsnReader revReader = seqReader.ReadSequence(tag); var revokedCertificates = new List(); while (revReader.HasData) { - var crlEntry = revReader.ReadSequence(); - var serial = crlEntry.ReadInteger(); + AsnReader crlEntry = revReader.ReadSequence(); + System.Numerics.BigInteger serial = crlEntry.ReadInteger(); var revokedCertificate = new RevokedCertificate(serial.ToByteArray()); - revokedCertificate.RevocationDate = ReadTime(crlEntry, optional: false); + revokedCertificate.RevocationDate = X509CRL.ReadTime(crlEntry, optional: false); if (version == 1 && crlEntry.HasData) { // CRL entry extensions - var crlEntryExtensions = crlEntry.ReadSequence(); + AsnReader crlEntryExtensions = crlEntry.ReadSequence(); while (crlEntryExtensions.HasData) { - var extension = crlEntryExtensions.ReadExtension(); + X509Extension extension = crlEntryExtensions.ReadExtension(); revokedCertificate.CrlEntryExtensions.Add(extension); } crlEntryExtensions.ThrowIfNotEmpty(); @@ -304,12 +304,12 @@ internal void DecodeCrl(byte[] tbs) seqReader.HasData) { var extTag = new Asn1Tag(TagClass.ContextSpecific, 0); - var optReader = seqReader.ReadSequence(extTag); + AsnReader optReader = seqReader.ReadSequence(extTag); var crlExtensionList = new X509ExtensionCollection(); - var crlExtensions = optReader.ReadSequence(); + AsnReader crlExtensions = optReader.ReadSequence(); while (crlExtensions.HasData) { - var extension = crlExtensions.ReadExtension(); + X509Extension extension = crlExtensions.ReadExtension(); crlExtensionList.Add(extension); } m_crlExtensions = crlExtensionList; @@ -332,10 +332,10 @@ internal void DecodeCrl(byte[] tbs) /// /// /// The DateTime representing the tag - private DateTime ReadTime(AsnReader asnReader, bool optional) + private static DateTime ReadTime(AsnReader asnReader, bool optional) { // determine if the time is UTC or GeneralizedTime time - var timeTag = asnReader.PeekTag(); + Asn1Tag timeTag = asnReader.PeekTag(); if (timeTag.TagValue == Asn1Tag.UtcTime.TagValue) { return asnReader.ReadUtcTime().UtcDateTime; diff --git a/Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Signature.cs b/Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Signature.cs index e2d64e58c..60ae459fc 100644 --- a/Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Signature.cs +++ b/Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Signature.cs @@ -80,7 +80,7 @@ public X509Signature(byte[] tbs, byte[] signature, byte[] signatureAlgorithmIden Tbs = tbs; Signature = signature; SignatureAlgorithmIdentifier = signatureAlgorithmIdentifier; - SignatureAlgorithm = DecodeAlgorithm(signatureAlgorithmIdentifier); + SignatureAlgorithm = X509Signature.DecodeAlgorithm(signatureAlgorithmIdentifier); Name = Oids.GetHashAlgorithmName(SignatureAlgorithm); } @@ -90,9 +90,9 @@ public X509Signature(byte[] tbs, byte[] signature, byte[] signatureAlgorithmIden /// X509 ASN format of EncodedData+SignatureOID+Signature bytes. public byte[] Encode() { - AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); + var writer = new AsnWriter(AsnEncodingRules.DER); - var tag = Asn1Tag.Sequence; + Asn1Tag tag = Asn1Tag.Sequence; writer.PushSequence(tag); // write Tbs encoded data @@ -128,15 +128,15 @@ private void Decode(byte[] crl) { try { - AsnReader crlReader = new AsnReader(crl, AsnEncodingRules.DER); - var seqReader = crlReader.ReadSequence(Asn1Tag.Sequence); + var crlReader = new AsnReader(crl, AsnEncodingRules.DER); + AsnReader seqReader = crlReader.ReadSequence(Asn1Tag.Sequence); if (seqReader != null) { // Tbs encoded data Tbs = seqReader.ReadEncodedValue().ToArray(); // Signature Algorithm Identifier - var sigOid = seqReader.ReadSequence(); + AsnReader sigOid = seqReader.ReadSequence(); SignatureAlgorithm = sigOid.ReadObjectIdentifier(); Name = Oids.GetHashAlgorithmName(SignatureAlgorithm); @@ -202,7 +202,7 @@ private bool VerifyForECDsa(X509Certificate2 certificate) { using (ECDsa key = certificate.GetECDsaPublicKey()) { - var decodedSignature = DecodeECDsa(Signature, key.KeySize); + byte[] decodedSignature = DecodeECDsa(Signature, key.KeySize); return key.VerifyData(Tbs, decodedSignature, Name); } } @@ -212,12 +212,12 @@ private bool VerifyForECDsa(X509Certificate2 certificate) /// /// The ASN.1 encoded algorithm oid. /// - private string DecodeAlgorithm(byte[] oid) + private static string DecodeAlgorithm(byte[] oid) { var seqReader = new AsnReader(oid, AsnEncodingRules.DER); - var sigOid = seqReader.ReadSequence(); + AsnReader sigOid = seqReader.ReadSequence(); seqReader.ThrowIfNotEmpty(); - var result = sigOid.ReadObjectIdentifier(); + string result = sigOid.ReadObjectIdentifier(); if (sigOid.HasData) { sigOid.ReadNull(); @@ -235,8 +235,8 @@ private static byte[] EncodeECDsa(byte[] signature) // Encode from IEEE signature format to ASN1 DER encoded // signature format for ecdsa certificates. // ECDSA-Sig-Value ::= SEQUENCE { r INTEGER, s INTEGER } - AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); - var tag = Asn1Tag.Sequence; + var writer = new AsnWriter(AsnEncodingRules.DER); + Asn1Tag tag = Asn1Tag.Sequence; writer.PushSequence(tag); int segmentLength = signature.Length / 2; @@ -255,11 +255,11 @@ private static byte[] EncodeECDsa(byte[] signature) /// The keySize in bits. private static byte[] DecodeECDsa(ReadOnlyMemory signature, int keySize) { - AsnReader reader = new AsnReader(signature, AsnEncodingRules.DER); - var seqReader = reader.ReadSequence(); + var reader = new AsnReader(signature, AsnEncodingRules.DER); + AsnReader seqReader = reader.ReadSequence(); reader.ThrowIfNotEmpty(); - var r = seqReader.ReadIntegerBytes(); - var s = seqReader.ReadIntegerBytes(); + ReadOnlyMemory r = seqReader.ReadIntegerBytes(); + ReadOnlyMemory s = seqReader.ReadIntegerBytes(); seqReader.ThrowIfNotEmpty(); keySize >>= 3; if (r.Span[0] == 0 && r.Length > keySize) @@ -270,7 +270,7 @@ private static byte[] DecodeECDsa(ReadOnlyMemory signature, int keySize) { s = s.Slice(1); } - var result = new byte[2 * keySize]; + byte[] result = new byte[2 * keySize]; int offset = keySize - r.Length; r.CopyTo(new Memory(result, offset, r.Length)); offset = 2 * keySize - s.Length; diff --git a/Libraries/Opc.Ua.Server/Aggregates/AggregateManager.cs b/Libraries/Opc.Ua.Server/Aggregates/AggregateManager.cs index 3cd97d4ee..76df4321c 100644 --- a/Libraries/Opc.Ua.Server/Aggregates/AggregateManager.cs +++ b/Libraries/Opc.Ua.Server/Aggregates/AggregateManager.cs @@ -235,7 +235,7 @@ public void RegisterFactory(NodeId aggregateId) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private IServerInternal m_server; private AggregateConfiguration m_defaultConfiguration; private Dictionary m_factories; diff --git a/Libraries/Opc.Ua.Server/Configuration/TrustList.cs b/Libraries/Opc.Ua.Server/Configuration/TrustList.cs index e27c04c75..a2f7da2a0 100644 --- a/Libraries/Opc.Ua.Server/Configuration/TrustList.cs +++ b/Libraries/Opc.Ua.Server/Configuration/TrustList.cs @@ -690,7 +690,7 @@ private void HasSecureWriteAccess(ISystemContext context) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private SecureAccess m_readAccess; private SecureAccess m_writeAccess; private NodeId m_sessionId; diff --git a/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs b/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs index 2614b10a1..405a9d174 100644 --- a/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs @@ -4772,7 +4772,7 @@ protected NodeState AddNodeToComponentCache(ISystemContext context, NodeHandle h #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private IServerInternal m_server; private ServerSystemContext m_systemContext; private string[] m_namespaceUris; diff --git a/Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs b/Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs index 145993933..7644ec5c4 100644 --- a/Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs @@ -1389,7 +1389,7 @@ private void FilterOutUnAuthorized(IList list, NodeId sessionId, ISystemCo if ((sessionId != context.SessionId) && !HasApplicationSecureAdminAccess(context)) { - list[index] = default(T); + list[index] = default; } } diff --git a/Libraries/Opc.Ua.Server/NodeManager/ContinuationPoint.cs b/Libraries/Opc.Ua.Server/NodeManager/ContinuationPoint.cs index e608e950c..9e439b50b 100644 --- a/Libraries/Opc.Ua.Server/NodeManager/ContinuationPoint.cs +++ b/Libraries/Opc.Ua.Server/NodeManager/ContinuationPoint.cs @@ -28,8 +28,6 @@ * ======================================================================*/ using System; -using System.Collections.Generic; -using System.Text; namespace Opc.Ua.Server { @@ -47,13 +45,13 @@ public ContinuationPoint() { } #endregion - + #region IDisposable Members /// /// Frees any unmanaged resources. /// public void Dispose() - { + { Dispose(true); GC.SuppressFinalize(this); } @@ -63,7 +61,7 @@ public void Dispose() /// protected virtual void Dispose(bool disposing) { - if (disposing) + if (disposing) { Utils.SilentDispose(m_data); } @@ -76,88 +74,88 @@ protected virtual void Dispose(bool disposing) /// public Guid Id { - get { return m_id; } + get { return m_id; } set { m_id = value; } } - + /// /// The node manager that created the continuation point. /// public INodeManager Manager { - get { return m_manager; } + get { return m_manager; } set { m_manager = value; } } - + /// /// The view being browsed. /// public ViewDescription View { - get { return m_view; } + get { return m_view; } set { m_view = value; } } - + /// /// The node being browsed. /// public object NodeToBrowse { - get { return m_nodeToBrowse; } + get { return m_nodeToBrowse; } set { m_nodeToBrowse = value; } } - + /// /// The maximum number of results to return. /// public uint MaxResultsToReturn { - get { return m_maxResultsToReturn; } + get { return m_maxResultsToReturn; } set { m_maxResultsToReturn = value; } } - + /// /// What direction to follow the references. /// public BrowseDirection BrowseDirection { - get { return m_browseDirection; } + get { return m_browseDirection; } set { m_browseDirection = value; } } - + /// /// The reference type of the references to return. /// public NodeId ReferenceTypeId { - get { return m_referenceTypeId; } + get { return m_referenceTypeId; } set { m_referenceTypeId = value; } } - + /// /// Whether subtypes of the reference type should be return as well. /// public bool IncludeSubtypes { - get { return m_includeSubtypes; } + get { return m_includeSubtypes; } set { m_includeSubtypes = value; } } - + /// /// The node class of the target nodes for the references to return. /// public uint NodeClassMask { - get { return m_nodeClassMask; } + get { return m_nodeClassMask; } set { m_nodeClassMask = value; } } - + /// /// The values to return. /// public BrowseResultMask ResultMask { - get { return m_resultMask; } + get { return m_resultMask; } set { m_resultMask = value; } } @@ -166,10 +164,10 @@ public BrowseResultMask ResultMask /// public int Index { - get { return m_index; } + get { return m_index; } set { m_index = value; } } - + /// /// Node manager specific data that is necessary to continue the browse. /// @@ -181,9 +179,9 @@ public int Index /// public object Data { - get { return m_data; } + get { return m_data; } set { m_data = value; } - } + } /// /// Whether the ReferenceTypeId should be returned in the result. @@ -192,7 +190,7 @@ public bool ReferenceTypeIdRequired { get { return (m_resultMask & BrowseResultMask.ReferenceTypeId) != 0; } } - + /// /// Whether the IsForward flag should be returned in the result. /// @@ -200,7 +198,7 @@ public bool IsForwardRequired { get { return (m_resultMask & BrowseResultMask.IsForward) != 0; } } - + /// /// Whether the NodeClass should be returned in the result. /// @@ -208,7 +206,7 @@ public bool NodeClassRequired { get { return (m_resultMask & BrowseResultMask.NodeClass) != 0; } } - + /// /// Whether the BrowseName should be returned in the result. /// @@ -216,7 +214,7 @@ public bool BrowseNameRequired { get { return (m_resultMask & BrowseResultMask.BrowseName) != 0; } } - + /// /// Whether the DisplayName should be returned in the result. /// @@ -224,7 +222,7 @@ public bool DisplayNameRequired { get { return (m_resultMask & BrowseResultMask.DisplayName) != 0; } } - + /// /// Whether the TypeDefinition should be returned in the result. /// @@ -232,7 +230,7 @@ public bool TypeDefinitionRequired { get { return (m_resultMask & BrowseResultMask.TypeDefinition) != 0; } } - + /// /// False if it is not necessary to read the attributes a target node. /// @@ -241,14 +239,14 @@ public bool TypeDefinitionRequired /// public bool TargetAttributesRequired { - get - { + get + { if (m_nodeClassMask != 0) { return true; } - return (m_resultMask & (BrowseResultMask.NodeClass | BrowseResultMask.BrowseName | BrowseResultMask.DisplayName | BrowseResultMask.TypeDefinition)) != 0; + return (m_resultMask & (BrowseResultMask.NodeClass | BrowseResultMask.BrowseName | BrowseResultMask.DisplayName | BrowseResultMask.TypeDefinition)) != 0; } } #endregion diff --git a/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs b/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs index cc331258c..121634526 100644 --- a/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs +++ b/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs @@ -3258,7 +3258,7 @@ private NodeId CreateUniqueNodeId(ushort namespaceIndex) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private IServerInternal m_server; private NodeTable m_nodes; private long m_lastId; diff --git a/Libraries/Opc.Ua.Server/NodeManager/EventManager.cs b/Libraries/Opc.Ua.Server/NodeManager/EventManager.cs index af3908f59..ed25e3586 100644 --- a/Libraries/Opc.Ua.Server/NodeManager/EventManager.cs +++ b/Libraries/Opc.Ua.Server/NodeManager/EventManager.cs @@ -249,7 +249,7 @@ public IList GetMonitoredItems() #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private IServerInternal m_server; private Dictionary m_monitoredItems; private uint m_maxEventQueueSize; diff --git a/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs b/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs index 49d2225ea..b2f3f442a 100644 --- a/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs +++ b/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs @@ -3346,7 +3346,7 @@ protected internal static ServiceResult ValidateRolePermissions(OperationContext #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private IServerInternal m_server; private List m_nodeManagers; private long m_lastMonitoredItemId; diff --git a/Libraries/Opc.Ua.Server/NodeManager/ResourceManager.cs b/Libraries/Opc.Ua.Server/NodeManager/ResourceManager.cs index a54519a99..481bb9189 100644 --- a/Libraries/Opc.Ua.Server/NodeManager/ResourceManager.cs +++ b/Libraries/Opc.Ua.Server/NodeManager/ResourceManager.cs @@ -569,7 +569,7 @@ private LocalizedText TranslateSymbolicId(IList preferredLocales, string #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] private IServerInternal m_server; private List m_translationTables; diff --git a/Libraries/Opc.Ua.Server/NodeManager/SamplingGroup.cs b/Libraries/Opc.Ua.Server/NodeManager/SamplingGroup.cs index ae824d858..31422a894 100644 --- a/Libraries/Opc.Ua.Server/NodeManager/SamplingGroup.cs +++ b/Libraries/Opc.Ua.Server/NodeManager/SamplingGroup.cs @@ -476,7 +476,7 @@ private void DoSample(object state) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private IServerInternal m_server; private INodeManager m_nodeManager; private Session m_session; diff --git a/Libraries/Opc.Ua.Server/NodeManager/SamplingGroupManager.cs b/Libraries/Opc.Ua.Server/NodeManager/SamplingGroupManager.cs index 74eb0db9d..fba59ec47 100644 --- a/Libraries/Opc.Ua.Server/NodeManager/SamplingGroupManager.cs +++ b/Libraries/Opc.Ua.Server/NodeManager/SamplingGroupManager.cs @@ -497,7 +497,7 @@ public virtual void ApplyChanges() #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private IServerInternal m_server; private INodeManager m_nodeManager; private List m_samplingGroups; diff --git a/Libraries/Opc.Ua.Server/Server/RequestManager.cs b/Libraries/Opc.Ua.Server/Server/RequestManager.cs index 47bf82253..874cdd677 100644 --- a/Libraries/Opc.Ua.Server/Server/RequestManager.cs +++ b/Libraries/Opc.Ua.Server/Server/RequestManager.cs @@ -250,11 +250,11 @@ private void OnTimerExpired(object state) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] private IServerInternal m_server; private Dictionary m_requests; - private object m_requestsLock = new object(); + private readonly object m_requestsLock = new object(); private Timer m_requestTimer; private event RequestCancelledEventHandler m_RequestCancelled; #endregion diff --git a/Libraries/Opc.Ua.Server/Server/ReverseConnectServer.cs b/Libraries/Opc.Ua.Server/Server/ReverseConnectServer.cs index 1ba518016..08acee92c 100644 --- a/Libraries/Opc.Ua.Server/Server/ReverseConnectServer.cs +++ b/Libraries/Opc.Ua.Server/Server/ReverseConnectServer.cs @@ -443,7 +443,7 @@ private void UpdateConfiguration(ApplicationConfiguration configuration) private int m_connectTimeout; private int m_rejectTimeout; private Dictionary m_connections; - private object m_connectionsLock = new object(); + private readonly object m_connectionsLock = new object(); #endregion } } diff --git a/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs b/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs index f95787c7e..ba9f82df2 100644 --- a/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs +++ b/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs @@ -876,7 +876,7 @@ private PropertyState SetPropertyValue(PropertyState property, uint private SessionManager m_sessionManager; private SubscriptionManager m_subscriptionManager; - private object m_dataLock = new object(); + private readonly object m_dataLock = new object(); private ServerObjectState m_serverObject; private ServerStatusValue m_serverStatus; private bool m_auditing; diff --git a/Libraries/Opc.Ua.Server/Session/Session.cs b/Libraries/Opc.Ua.Server/Session/Session.cs index 541d0b886..9d7e384ac 100644 --- a/Libraries/Opc.Ua.Server/Session/Session.cs +++ b/Libraries/Opc.Ua.Server/Session/Session.cs @@ -407,13 +407,6 @@ public virtual void ValidateRequest(RequestHeader requestHeader, RequestType req } } - // verify timestamp. - if (requestHeader.Timestamp.AddMilliseconds(m_maxRequestAge) < DateTime.UtcNow) - { - UpdateDiagnosticCounters(requestType, true, false); - throw new ServiceResultException(StatusCodes.BadInvalidTimestamp); - } - // request accepted. UpdateDiagnosticCounters(requestType, false, false); } diff --git a/Libraries/Opc.Ua.Server/Session/SessionManager.cs b/Libraries/Opc.Ua.Server/Session/SessionManager.cs index de3d760c8..ff2b206d1 100644 --- a/Libraries/Opc.Ua.Server/Session/SessionManager.cs +++ b/Libraries/Opc.Ua.Server/Session/SessionManager.cs @@ -624,7 +624,7 @@ private void MonitorSessions(object data) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private IServerInternal m_server; private Dictionary m_sessions; private long m_lastSessionId; @@ -638,7 +638,7 @@ private void MonitorSessions(object data) private int m_maxHistoryContinuationPoints; private int m_minNonceLength; - private object m_eventLock = new object(); + private readonly object m_eventLock = new object(); private event SessionEventHandler m_sessionCreated; private event SessionEventHandler m_sessionActivated; private event SessionEventHandler m_sessionClosing; diff --git a/Libraries/Opc.Ua.Server/Subscription/MonitoredItem.cs b/Libraries/Opc.Ua.Server/Subscription/MonitoredItem.cs index bafc5571b..47c43c714 100644 --- a/Libraries/Opc.Ua.Server/Subscription/MonitoredItem.cs +++ b/Libraries/Opc.Ua.Server/Subscription/MonitoredItem.cs @@ -1820,7 +1820,7 @@ private void QueueOverflowHandler() #endregion #region Private Members - private object m_lock = new object(); + private readonly object m_lock = new object(); private IServerInternal m_server; private INodeManager m_nodeManager; private object m_managerHandle; diff --git a/Libraries/Opc.Ua.Server/Subscription/SessionPublishQueue.cs b/Libraries/Opc.Ua.Server/Subscription/SessionPublishQueue.cs index 766c2c7e4..1f711f1e5 100644 --- a/Libraries/Opc.Ua.Server/Subscription/SessionPublishQueue.cs +++ b/Libraries/Opc.Ua.Server/Subscription/SessionPublishQueue.cs @@ -914,7 +914,7 @@ internal void TraceState(string context, params object[] args) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private IServerInternal m_server; private Session m_session; private ManualResetEvent m_publishEvent; diff --git a/Libraries/Opc.Ua.Server/Subscription/Subscription.cs b/Libraries/Opc.Ua.Server/Subscription/Subscription.cs index ff7ccfdd0..ee1cb627d 100644 --- a/Libraries/Opc.Ua.Server/Subscription/Subscription.cs +++ b/Libraries/Opc.Ua.Server/Subscription/Subscription.cs @@ -2410,7 +2410,7 @@ private void TraceState(LogLevel logLevel, TraceStateId id, string context) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private IServerInternal m_server; private Session m_session; private uint m_id; diff --git a/Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs b/Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs index b90b45aab..0456e061e 100644 --- a/Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs +++ b/Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs @@ -66,6 +66,7 @@ public SubscriptionManager( m_subscriptions = new Dictionary(); m_publishQueues = new Dictionary(); m_statusMessages = new Dictionary>(); + m_lastSubscriptionId = BitConverter.ToInt64(Utils.Nonce.CreateNonce(sizeof(long)), 0); // create a event to signal shutdown. m_shutdownEvent = new ManualResetEvent(true); @@ -2048,7 +2049,7 @@ public override int GetHashCode() #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private long m_lastSubscriptionId; private IServerInternal m_server; private double m_minPublishingInterval; @@ -2068,9 +2069,9 @@ public override int GetHashCode() private Queue m_conditionRefreshQueue; private ManualResetEvent m_conditionRefreshEvent; - private object m_statusMessagesLock = new object(); - private object m_eventLock = new object(); - private object m_conditionRefreshLock = new object(); + private readonly object m_statusMessagesLock = new object(); + private readonly object m_eventLock = new object(); + private readonly object m_conditionRefreshLock = new object(); private event SubscriptionEventHandler m_SubscriptionCreated; private event SubscriptionEventHandler m_SubscriptionDeleted; #endregion diff --git a/README.md b/README.md index 3f9e748c7..aa029a7b9 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ More samples based on the official [Nuget](https://www.nuget.org/packages/OPCFou [![Github Actions](https://github.com/OPCFoundation/UA-.NETStandard/actions/workflows/buildandtest.yml/badge.svg)](https://github.com/OPCFoundation/UA-.NETStandard/actions/workflows/buildandtest.yml) ### Code Quality -[![Test Status](https://img.shields.io/azure-devops/tests/opcfoundation/opcua-netstandard/14?style=plastic)](https://opcfoundation.visualstudio.com/opcua-netstandard/_test/analytics?definitionId=14&contextType=build) +[![Tests](https://img.shields.io/azure-devops/tests/opcfoundation/opcua-netstandard/14/master?style=plastic&label=Tests)](https://opcfoundation.visualstudio.com/opcua-netstandard/_test/analytics?definitionId=14&contextType=build) [![CodeQL](https://github.com/OPCFoundation/UA-.NETStandard/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/OPCFoundation/UA-.NETStandard/actions/workflows/codeql-analysis.yml) [![Coverage Status](https://codecov.io/gh/OPCFoundation/UA-.NETStandard/branch/master/graph/badge.svg?token=vDf5AnilUt)](https://codecov.io/gh/OPCFoundation/UA-.NETStandard) @@ -71,8 +71,8 @@ All the tools you need for .NET Standard come with the .NET Core tools. See [Get Note: Since .NET Core 2.1 is end of life, - VS 2017 has only limited support for .NET 4.8. -- VS 2019 is fully supported with .NET 4.8 and up to .NET Core 3.1 (end of life). -- VS 2022 is the current supported version, including .NET 6.0 (LTS). +- VS 2019 has only limited support for .NET 4.8 because .NET Core 3.1 reached end of life. +- VS 2022 is the current supported version, including .NET 6.0 (LTS) and .NET 8.0 (LTS). 1. Open the UA Reference.sln solution file using Visual Studio. 2. Choose a project in the Solution Explorer and set it with a right click as `Startup Project`. diff --git a/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj b/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj index d709e861b..2ce01329f 100644 --- a/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj +++ b/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj @@ -19,7 +19,7 @@ - + @@ -33,7 +33,7 @@ - + diff --git a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportChannel.cs b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportChannel.cs index 417ba7fa0..4ef389e0d 100644 --- a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportChannel.cs +++ b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportChannel.cs @@ -235,6 +235,13 @@ public void Close() m_client?.Dispose(); } + /// + public Task CloseAsync(CancellationToken ct) + { + Close(); + return Task.CompletedTask; + } + /// /// The async result class for the Https transport. /// @@ -335,6 +342,12 @@ public IServiceResponse EndSendRequest(IAsyncResult result) return result2 as IServiceResponse; } + /// + public Task EndSendRequestAsync(IAsyncResult result, CancellationToken ct) + { + throw new NotImplementedException(); + } + /// /// Not implemented here. public IAsyncResult BeginOpen(AsyncCallback callback, object callbackData) diff --git a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj index 7462d3f59..17985145e 100644 --- a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj +++ b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj @@ -41,13 +41,13 @@ - + use latest versions only on .NET 5/6/7/8, otherwise 3.1.x --> + - + - + @@ -58,12 +58,12 @@ - + - + @@ -81,18 +81,18 @@ - + $(BaseIntermediateOutputPath)/zipnodeset2 Schema/Opc.Ua.NodeSet2.xml - + - + diff --git a/Stack/Opc.Ua.Core/Schema/UANodeSetHelpers.cs b/Stack/Opc.Ua.Core/Schema/UANodeSetHelpers.cs index 5eda096db..b2e74bfbf 100644 --- a/Stack/Opc.Ua.Core/Schema/UANodeSetHelpers.cs +++ b/Stack/Opc.Ua.Core/Schema/UANodeSetHelpers.cs @@ -669,16 +669,13 @@ private NodeState Import(ISystemContext context, UANode node) if (node.References != null) { - BaseInstanceState instance = importedNode as BaseInstanceState; - BaseTypeState type = importedNode as BaseTypeState; - for (int ii = 0; ii < node.References.Length; ii++) { Opc.Ua.NodeId referenceTypeId = ImportNodeId(node.References[ii].ReferenceType, context.NamespaceUris, true); bool isInverse = !node.References[ii].IsForward; Opc.Ua.ExpandedNodeId targetId = ImportExpandedNodeId(node.References[ii].Value, context.NamespaceUris, context.ServerUris); - if (instance != null) + if (importedNode is BaseInstanceState instance) { if (referenceTypeId == ReferenceTypeIds.HasModellingRule && !isInverse) { @@ -693,7 +690,7 @@ private NodeState Import(ISystemContext context, UANode node) } } - if (type != null) + if (importedNode is BaseTypeState type) { if (referenceTypeId == ReferenceTypeIds.HasSubtype && isInverse) { @@ -922,9 +919,8 @@ private Opc.Ua.Export.DataTypeDefinition Export( definition.SymbolicName = dataType.SymbolicName; } - StructureDefinition sd = source.Body as StructureDefinition; - if (sd != null) + if (source.Body is StructureDefinition sd) { if (sd.StructureType == StructureType.Union || sd.StructureType == StructureType.UnionWithSubtypedValues) { @@ -989,9 +985,8 @@ private Opc.Ua.Export.DataTypeDefinition Export( } } - EnumDefinition ed = source.Body as EnumDefinition; - if (ed != null) + if (source.Body is EnumDefinition ed) { definition.IsOptionSet = ed.IsOptionSet; diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs index fa9d8a3dc..3c92ac5b4 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs @@ -614,7 +614,7 @@ private static void SetSuitableDefaults( } #endregion - private static Dictionary m_certificates = new Dictionary(); - private static object m_certificatesLock = new object(); + private static readonly Dictionary m_certificates = new Dictionary(); + private static readonly object m_certificatesLock = new object(); } } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs index 999d09d35..55784cfa7 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs @@ -82,9 +82,8 @@ public override bool Equals(object obj) return true; } - CertificateIdentifier id = obj as CertificateIdentifier; - if (id == null) + if (!(obj is CertificateIdentifier id)) { return false; } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateStoreIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateStoreIdentifier.cs index 59af44147..8bcd9e6c3 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateStoreIdentifier.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateStoreIdentifier.cs @@ -234,14 +234,22 @@ public static void RegisterCertificateStoreType(string storeTypeName, ICertifica #endregion #region Internal Methods - internal static ICertificateStoreType GetCertificateStoreTypeByName(string storeTypeName) + /// + /// Returns the registered type for a custom certificate store. + /// + /// + /// + public static ICertificateStoreType GetCertificateStoreTypeByName(string storeTypeName) { ICertificateStoreType result; s_registeredStoreTypes.TryGetValue(storeTypeName, out result); return result; } - internal static IReadOnlyCollection RegisteredStoreTypeNames => s_registeredStoreTypes.Keys; + /// + /// Returns the collection of registered certificate store keys. + /// + public static IReadOnlyCollection RegisteredStoreTypeNames => s_registeredStoreTypes.Keys; #endregion #region Data Members diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateTrustList.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateTrustList.cs index 365f4ed13..36ac98130 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateTrustList.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateTrustList.cs @@ -107,7 +107,7 @@ public async Task GetCertificates() #endregion #region Private Members - private object m_lock; + private object m_lock = new object(); private ICertificateStore m_store; #endregion } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index 4dd2815d0..377b156ef 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -16,6 +16,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Opc.Ua.Security.Certificates; @@ -110,53 +111,70 @@ public virtual void Update( CertificateTrustList trustedStore, CertificateStoreIdentifier rejectedCertificateStore) { - lock (m_lock) + try { - ResetValidatedCertificates(); + m_semaphore.Wait(); - m_trustedCertificateStore = null; - m_trustedCertificateList = null; + InternalUpdate(issuerStore, trustedStore, rejectedCertificateStore); + } + finally + { + m_semaphore.Release(); + } + } - if (trustedStore != null) - { - m_trustedCertificateStore = new CertificateStoreIdentifier(); + /// + /// Updates the validator with a new set of trust lists. + /// + private void InternalUpdate( + CertificateTrustList issuerStore, + CertificateTrustList trustedStore, + CertificateStoreIdentifier rejectedCertificateStore) + { + InternalResetValidatedCertificates(); - m_trustedCertificateStore.StoreType = trustedStore.StoreType; - m_trustedCertificateStore.StorePath = trustedStore.StorePath; - m_trustedCertificateStore.ValidationOptions = trustedStore.ValidationOptions; + m_trustedCertificateStore = null; + m_trustedCertificateList = null; - if (trustedStore.TrustedCertificates != null) - { - m_trustedCertificateList = new CertificateIdentifierCollection(); - m_trustedCertificateList.AddRange(trustedStore.TrustedCertificates); - } - } + if (trustedStore != null) + { + m_trustedCertificateStore = new CertificateStoreIdentifier(); - m_issuerCertificateStore = null; - m_issuerCertificateList = null; + m_trustedCertificateStore.StoreType = trustedStore.StoreType; + m_trustedCertificateStore.StorePath = trustedStore.StorePath; + m_trustedCertificateStore.ValidationOptions = trustedStore.ValidationOptions; - if (issuerStore != null) + if (trustedStore.TrustedCertificates != null) { - m_issuerCertificateStore = new CertificateStoreIdentifier(); + m_trustedCertificateList = new CertificateIdentifierCollection(); + m_trustedCertificateList.AddRange(trustedStore.TrustedCertificates); + } + } - m_issuerCertificateStore.StoreType = issuerStore.StoreType; - m_issuerCertificateStore.StorePath = issuerStore.StorePath; - m_issuerCertificateStore.ValidationOptions = issuerStore.ValidationOptions; + m_issuerCertificateStore = null; + m_issuerCertificateList = null; - if (issuerStore.TrustedCertificates != null) - { - m_issuerCertificateList = new CertificateIdentifierCollection(); - m_issuerCertificateList.AddRange(issuerStore.TrustedCertificates); - } - } + if (issuerStore != null) + { + m_issuerCertificateStore = new CertificateStoreIdentifier(); - m_rejectedCertificateStore = null; + m_issuerCertificateStore.StoreType = issuerStore.StoreType; + m_issuerCertificateStore.StorePath = issuerStore.StorePath; + m_issuerCertificateStore.ValidationOptions = issuerStore.ValidationOptions; - if (rejectedCertificateStore != null) + if (issuerStore.TrustedCertificates != null) { - m_rejectedCertificateStore = (CertificateStoreIdentifier)rejectedCertificateStore.MemberwiseClone(); + m_issuerCertificateList = new CertificateIdentifierCollection(); + m_issuerCertificateList.AddRange(issuerStore.TrustedCertificates); } } + + m_rejectedCertificateStore = null; + + if (rejectedCertificateStore != null) + { + m_rejectedCertificateStore = (CertificateStoreIdentifier)rejectedCertificateStore.MemberwiseClone(); + } } /// @@ -169,12 +187,15 @@ public virtual async Task Update(SecurityConfiguration configuration) throw new ArgumentNullException(nameof(configuration)); } - lock (m_lock) + try { - Update( + await m_semaphore.WaitAsync().ConfigureAwait(false); + + InternalUpdate( configuration.TrustedIssuerCertificates, configuration.TrustedPeerCertificates, configuration.RejectedCertificateStore); + // protect the flags if application called to set property if ((m_protectFlags & ProtectFlags.AutoAcceptUntrustedCertificates) == 0) { @@ -197,6 +218,10 @@ public virtual async Task Update(SecurityConfiguration configuration) m_useValidatedCertificates = configuration.UseValidatedCertificates; } } + finally + { + m_semaphore.Release(); + } if (configuration.ApplicationCertificate != null) { @@ -209,13 +234,20 @@ public virtual async Task Update(SecurityConfiguration configuration) /// public virtual async Task UpdateCertificate(SecurityConfiguration securityConfiguration) { - lock (m_lock) + try { + await m_semaphore.WaitAsync().ConfigureAwait(false); + securityConfiguration.ApplicationCertificate.Certificate = null; + + await securityConfiguration.ApplicationCertificate.LoadPrivateKeyEx( + securityConfiguration.CertificatePasswordProvider).ConfigureAwait(false); + } + finally + { + m_semaphore.Release(); } - await securityConfiguration.ApplicationCertificate.LoadPrivateKeyEx( - securityConfiguration.CertificatePasswordProvider).ConfigureAwait(false); await Update(securityConfiguration).ConfigureAwait(false); lock (m_callbackLock) @@ -233,15 +265,29 @@ await securityConfiguration.ApplicationCertificate.LoadPrivateKeyEx( /// public void ResetValidatedCertificates() { - lock (m_lock) + try { - // dispose outdated list - foreach (var cert in m_validatedCertificates.Values) - { - Utils.SilentDispose(cert); - } - m_validatedCertificates.Clear(); + m_semaphore.Wait(); + + InternalResetValidatedCertificates(); + } + finally + { + m_semaphore.Release(); + } + } + + /// + /// Reset the list of validated certificates. + /// + private void InternalResetValidatedCertificates() + { + // dispose outdated list + foreach (var cert in m_validatedCertificates.Values) + { + Utils.SilentDispose(cert); } + m_validatedCertificates.Clear(); } /// @@ -252,15 +298,21 @@ public bool AutoAcceptUntrustedCertificates get => m_autoAcceptUntrustedCertificates; set { - lock (m_lock) + try { + m_semaphore.Wait(); + m_protectFlags |= ProtectFlags.AutoAcceptUntrustedCertificates; if (m_autoAcceptUntrustedCertificates != value) { m_autoAcceptUntrustedCertificates = value; - ResetValidatedCertificates(); + InternalResetValidatedCertificates(); } } + finally + { + m_semaphore.Release(); + } } } @@ -272,15 +324,21 @@ public bool RejectSHA1SignedCertificates get => m_rejectSHA1SignedCertificates; set { - lock (m_lock) + try { + m_semaphore.Wait(); + m_protectFlags |= ProtectFlags.RejectSHA1SignedCertificates; if (m_rejectSHA1SignedCertificates != value) { m_rejectSHA1SignedCertificates = value; - ResetValidatedCertificates(); + InternalResetValidatedCertificates(); } } + finally + { + m_semaphore.Release(); + } } } @@ -292,15 +350,21 @@ public bool RejectUnknownRevocationStatus get => m_rejectUnknownRevocationStatus; set { - lock (m_lock) + try { + m_semaphore.Wait(); + m_protectFlags |= ProtectFlags.RejectUnknownRevocationStatus; if (m_rejectUnknownRevocationStatus != value) { m_rejectUnknownRevocationStatus = value; - ResetValidatedCertificates(); + InternalResetValidatedCertificates(); } } + finally + { + m_semaphore.Release(); + } } } @@ -312,8 +376,10 @@ public ushort MinimumCertificateKeySize get => m_minimumCertificateKeySize; set { - lock (m_lock) + try { + m_semaphore.Wait(); + m_protectFlags |= ProtectFlags.MinimumCertificateKeySize; if (m_minimumCertificateKeySize != value) { @@ -321,6 +387,10 @@ public ushort MinimumCertificateKeySize ResetValidatedCertificates(); } } + finally + { + m_semaphore.Release(); + } } } @@ -332,8 +402,10 @@ public bool UseValidatedCertificates get => m_useValidatedCertificates; set { - lock (m_lock) + try { + m_semaphore.Wait(); + m_protectFlags |= ProtectFlags.UseValidatedCertificates; if (m_useValidatedCertificates != value) { @@ -341,6 +413,11 @@ public bool UseValidatedCertificates ResetValidatedCertificates(); } } + finally + { + m_semaphore.Release(); + } + } } @@ -357,123 +434,205 @@ public void Validate(X509Certificate2 certificate) /// Validates a certificate. /// /// - /// Each UA application may have a list of trusted certificates that is different from + /// Each UA application may have a list of trusted certificates that is different from /// all other UA applications that may be running on the same machine. As a result, the /// certificate validator cannot rely completely on the Windows certificate store and /// user or machine specific CTLs (certificate trust lists). /// - public virtual void Validate(X509Certificate2Collection chain) + public virtual void Validate(X509Certificate2Collection certificateChain) { - Validate(chain, null); + Validate(certificateChain, null); + } + + /// + public Task ValidateAsync(X509Certificate2 certificate, CancellationToken ct) + { + return ValidateAsync(new X509Certificate2Collection() { certificate }, ct); + } + + /// + public virtual Task ValidateAsync(X509Certificate2Collection chain, CancellationToken ct) + { + return ValidateAsync(chain, null, ct); } /// /// Validates a certificate with domain validation check. - /// + /// /// - public virtual void Validate(X509Certificate2Collection chain, ConfiguredEndpoint endpoint) + public virtual async Task ValidateAsync(X509Certificate2Collection chain, ConfiguredEndpoint endpoint, CancellationToken ct) { X509Certificate2 certificate = chain[0]; try { - lock (m_lock) + await m_semaphore.WaitAsync(ct).ConfigureAwait(false); + + try { - InternalValidate(chain, endpoint).GetAwaiter().GetResult(); + await InternalValidateAsync(chain, endpoint, ct).ConfigureAwait(false); // add to list of validated certificates. m_validatedCertificates[certificate.Thumbprint] = new X509Certificate2(certificate.RawData); + + return; + } + finally + { + m_semaphore.Release(); } } catch (ServiceResultException se) { - // check for errors that may be suppressed. - if (ContainsUnsuppressibleSC(se.Result)) + HandleCertificateValidationException(se, certificate, chain); + } + + // add to list of peers. + await m_semaphore.WaitAsync(ct).ConfigureAwait(false); + try + { + Utils.LogCertificate(LogLevel.Warning, "Validation errors suppressed: ", certificate); + m_validatedCertificates[certificate.Thumbprint] = new X509Certificate2(certificate.RawData); + } + finally + { + m_semaphore.Release(); + } + } + + /// + /// Validates a certificate with domain validation check. + /// + /// + public virtual void Validate(X509Certificate2Collection chain, ConfiguredEndpoint endpoint) + { + X509Certificate2 certificate = chain[0]; + + try + { + try { - Utils.LogCertificate(LogLevel.Error, "Certificate rejected. Reason={0}.", - certificate, se.Result.StatusCode); + m_semaphore.Wait(); - // save the chain in rejected store to allow to add certs to a trusted or issuer store - SaveCertificates(chain); + InternalValidateAsync(chain, endpoint).GetAwaiter().GetResult(); - LogInnerServiceResults(LogLevel.Error, se.Result.InnerResult); - throw new ServiceResultException(se, StatusCodes.BadCertificateInvalid); + // add to list of validated certificates. + m_validatedCertificates[certificate.Thumbprint] = new X509Certificate2(certificate.RawData); + + return; } - else + finally { - Utils.LogCertificate(LogLevel.Warning, "Certificate Validation failed. Reason={0}.", - certificate, se.Result.StatusCode); - LogInnerServiceResults(LogLevel.Warning, se.Result.InnerResult); + m_semaphore.Release(); } + } + catch (ServiceResultException se) + { + HandleCertificateValidationException(se, certificate, chain); + } - // invoke callback. - bool accept = false; - string applicationErrorMsg = string.Empty; + // add to list of peers. + try + { + m_semaphore.Wait(); - ServiceResult serviceResult = se.Result; - lock (m_callbackLock) + Utils.LogCertificate(LogLevel.Warning, "Validation errors suppressed: ", certificate); + m_validatedCertificates[certificate.Thumbprint] = new X509Certificate2(certificate.RawData); + } + finally + { + m_semaphore.Release(); + } + } + + /// + /// + /// + /// + /// + /// + /// + private void HandleCertificateValidationException(ServiceResultException se, X509Certificate2 certificate, X509Certificate2Collection chain) + { + // check for errors that may be suppressed. + if (ContainsUnsuppressibleSC(se.Result)) + { + Utils.LogCertificate(LogLevel.Error, "Certificate rejected. Reason={0}.", + certificate, se.Result.StatusCode); + + // save the chain in rejected store to allow to add certs to a trusted or issuer store + SaveCertificates(chain); + + LogInnerServiceResults(LogLevel.Error, se.Result.InnerResult); + throw new ServiceResultException(se, StatusCodes.BadCertificateInvalid); + } + else + { + Utils.LogCertificate(LogLevel.Warning, "Certificate Validation failed. Reason={0}.", + certificate, se.Result.StatusCode); + LogInnerServiceResults(LogLevel.Warning, se.Result.InnerResult); + } + + // invoke callback. + bool accept = false; + string applicationErrorMsg = string.Empty; + + ServiceResult serviceResult = se.Result; + lock (m_callbackLock) + { + do { - do + accept = false; + if (m_CertificateValidation != null) { - accept = false; - if (m_CertificateValidation != null) - { - CertificateValidationEventArgs args = new CertificateValidationEventArgs(serviceResult, certificate); - m_CertificateValidation(this, args); - if (args.AcceptAll) - { - accept = true; - serviceResult = null; - break; - } - applicationErrorMsg = args.ApplicationErrorMsg; - accept = args.Accept; - } - else if (m_autoAcceptUntrustedCertificates && - serviceResult.StatusCode == StatusCodes.BadCertificateUntrusted) + CertificateValidationEventArgs args = new CertificateValidationEventArgs(serviceResult, certificate); + m_CertificateValidation(this, args); + if (args.AcceptAll) { accept = true; - Utils.LogCertificate("Auto accepted certificate: ", certificate); + serviceResult = null; + break; } + applicationErrorMsg = args.ApplicationErrorMsg; + accept = args.Accept; + } + else if (m_autoAcceptUntrustedCertificates && + serviceResult.StatusCode == StatusCodes.BadCertificateUntrusted) + { + accept = true; + Utils.LogCertificate("Auto accepted certificate: ", certificate); + } - if (accept) + if (accept) + { + serviceResult = serviceResult.InnerResult; + } + else + { + // report the rejected service result + if (string.IsNullOrEmpty(applicationErrorMsg)) { - serviceResult = serviceResult.InnerResult; + se = new ServiceResultException(serviceResult); } else { - // report the rejected service result - if (string.IsNullOrEmpty(applicationErrorMsg)) - { - se = new ServiceResultException(serviceResult); - } - else - { - se = new ServiceResultException(applicationErrorMsg); - } + se = new ServiceResultException(applicationErrorMsg); } - } while (accept && serviceResult != null); - } - - // throw if rejected. - if (!accept) - { - // write the invalid certificate chain to rejected store if specified. - Utils.LogCertificate(LogLevel.Error, "Certificate rejected. Reason={0}.", - certificate, serviceResult != null ? serviceResult.StatusCode.ToString() : "Unknown Error"); + } + } while (accept && serviceResult != null); + } - // save the chain in rejected store to allow to add cert to a trusted or issuer store - SaveCertificates(chain); + // throw if rejected. + if (!accept) + { + // write the invalid certificate chain to rejected store if specified. + Utils.LogCertificate(LogLevel.Error, "Certificate rejected. Reason={0}.", + certificate, serviceResult != null ? serviceResult.StatusCode.ToString() : "Unknown Error"); - throw new ServiceResultException(se, StatusCodes.BadCertificateInvalid); - } + // save the chain in rejected store to allow to add cert to a trusted or issuer store + SaveCertificates(chain); - // add to list of peers. - lock (m_lock) - { - Utils.LogCertificate(LogLevel.Warning, "Validation errors suppressed: ", certificate); - m_validatedCertificates[certificate.Thumbprint] = new X509Certificate2(certificate.RawData); - } + throw new ServiceResultException(se, StatusCodes.BadCertificateInvalid); } } @@ -524,8 +683,10 @@ private void SaveCertificate(X509Certificate2 certificate) /// private void SaveCertificates(X509Certificate2Collection certificateChain) { - lock (m_lock) + try { + m_semaphore.Wait(); + if (m_rejectedCertificateStore != null) { Utils.LogTrace("Writing rejected certificate chain to: {0}", m_rejectedCertificateStore); @@ -564,12 +725,16 @@ private void SaveCertificates(X509Certificate2Collection certificateChain) } } } + finally + { + m_semaphore.Release(); + } } /// /// Returns the certificate information for a trusted peer certificate. /// - private async Task GetTrustedCertificate(X509Certificate2 certificate) + private async Task GetTrustedCertificateAsync(X509Certificate2 certificate) { // check if explicitly trusted. if (m_trustedCertificateList != null) @@ -694,7 +859,7 @@ public async Task GetIssuersNoExceptionsOnGetIssuer(X509Certificate2Collec if (validationErrors != null) { - (issuer, revocationStatus) = await GetIssuerNoException(certificate, m_trustedCertificateList, m_trustedCertificateStore, true).ConfigureAwait(false); + (issuer, revocationStatus) = await GetIssuerNoExceptionAsync(certificate, m_trustedCertificateList, m_trustedCertificateStore, true).ConfigureAwait(false); } else { @@ -705,7 +870,7 @@ public async Task GetIssuersNoExceptionsOnGetIssuer(X509Certificate2Collec { if (validationErrors != null) { - (issuer, revocationStatus) = await GetIssuerNoException(certificate, m_issuerCertificateList, m_issuerCertificateStore, true).ConfigureAwait(false); + (issuer, revocationStatus) = await GetIssuerNoExceptionAsync(certificate, m_issuerCertificateList, m_issuerCertificateStore, true).ConfigureAwait(false); } else { @@ -716,7 +881,7 @@ public async Task GetIssuersNoExceptionsOnGetIssuer(X509Certificate2Collec { if (validationErrors != null) { - (issuer, revocationStatus) = await GetIssuerNoException(certificate, untrustedCollection, null, true).ConfigureAwait(false); + (issuer, revocationStatus) = await GetIssuerNoExceptionAsync(certificate, untrustedCollection, null, true).ConfigureAwait(false); } else { @@ -774,7 +939,7 @@ public Task GetIssuers(X509Certificate2 certificate, List /// Returns the certificate information for a trusted issuer certificate. /// - private async Task<(CertificateIdentifier, ServiceResultException)> GetIssuerNoException( + private async Task<(CertificateIdentifier, ServiceResultException)> GetIssuerNoExceptionAsync( X509Certificate2 certificate, CertificateIdentifierCollection explicitList, CertificateStoreIdentifier certificateStore, @@ -909,7 +1074,7 @@ private async Task GetIssuer( } (CertificateIdentifier result, ServiceResultException srex) = - await GetIssuerNoException(certificate, explicitList, certificateStore, checkRecovationStatus + await GetIssuerNoExceptionAsync(certificate, explicitList, certificateStore, checkRecovationStatus ).ConfigureAwait(false); if (srex != null) { @@ -923,8 +1088,9 @@ await GetIssuerNoException(certificate, explicitList, certificateStore, checkRec /// /// The certificates to be checked. /// The endpoint for domain validation. + /// The cancellation token. /// If certificate[0] cannot be accepted - protected virtual async Task InternalValidate(X509Certificate2Collection certificates, ConfiguredEndpoint endpoint) + protected virtual async Task InternalValidateAsync(X509Certificate2Collection certificates, ConfiguredEndpoint endpoint, CancellationToken ct = default) { X509Certificate2 certificate = certificates[0]; @@ -940,7 +1106,7 @@ protected virtual async Task InternalValidate(X509Certificate2Collection certifi } } - CertificateIdentifier trustedCertificate = await GetTrustedCertificate(certificate).ConfigureAwait(false); + CertificateIdentifier trustedCertificate = await GetTrustedCertificateAsync(certificate).ConfigureAwait(false); // get the issuers (checks the revocation lists if using directory stores). List issuers = new List(); @@ -959,7 +1125,7 @@ protected virtual async Task InternalValidate(X509Certificate2Collection certifi #if NET5_0_OR_GREATER DisableCertificateDownloads = true, #endif - }; + }; foreach (CertificateIdentifier issuer in issuers) { @@ -1025,6 +1191,23 @@ protected virtual async Task InternalValidate(X509Certificate2Collection certifi sresult = new ServiceResult(result, sresult); break; + // unexpected error status + case X509ChainStatusFlags.CtlNotSignatureValid: + case X509ChainStatusFlags.CtlNotTimeValid: + case X509ChainStatusFlags.CtlNotValidForUsage: + case X509ChainStatusFlags.Cyclic: + case X509ChainStatusFlags.ExplicitDistrust: + case X509ChainStatusFlags.HasExcludedNameConstraint: + case X509ChainStatusFlags.HasNotDefinedNameConstraint: + case X509ChainStatusFlags.HasNotPermittedNameConstraint: + case X509ChainStatusFlags.HasNotSupportedCriticalExtension: + case X509ChainStatusFlags.HasNotSupportedNameConstraint: + case X509ChainStatusFlags.HasWeakSignature: + case X509ChainStatusFlags.InvalidExtension: + case X509ChainStatusFlags.InvalidNameConstraints: + case X509ChainStatusFlags.InvalidPolicyConstraints: + case X509ChainStatusFlags.NoIssuanceChainPolicy: + // unexpected error status default: Utils.LogError("Unexpected status {0} processing certificate chain.", chainStatus.Status); @@ -1330,7 +1513,7 @@ private static ServiceResult CheckChainStatus(X509ChainStatus status, Certificat case X509ChainStatusFlags.NotValidForUsage: { return ServiceResult.Create( - (isIssuer) ? StatusCodes.BadCertificateUseNotAllowed : StatusCodes.BadCertificateIssuerUseNotAllowed, + isIssuer ? StatusCodes.BadCertificateUseNotAllowed : StatusCodes.BadCertificateIssuerUseNotAllowed, "Certificate may not be used as an application instance certificate. {0}: {1}", status.Status, status.StatusInformation); @@ -1347,8 +1530,8 @@ private static ServiceResult CheckChainStatus(X509ChainStatus status, Certificat goto case X509ChainStatusFlags.UntrustedRoot; case X509ChainStatusFlags.UntrustedRoot: { - // self signed cert signature validation - // .NET Core ChainStatus returns NotSignatureValid only on Windows, + // self signed cert signature validation + // .NET Core ChainStatus returns NotSignatureValid only on Windows, // so we have to do the extra cert signature check on all platforms if (issuer == null && id.Certificate != null && X509Utils.IsSelfSigned(id.Certificate)) @@ -1583,9 +1766,9 @@ private enum ProtectFlags #endregion #region Private Fields - private object m_lock = new object(); - private object m_callbackLock = new object(); - private Dictionary m_validatedCertificates; + private readonly SemaphoreSlim m_semaphore = new SemaphoreSlim(1, 1); + private readonly object m_callbackLock = new object(); + private readonly Dictionary m_validatedCertificates; private CertificateStoreIdentifier m_trustedCertificateStore; private CertificateIdentifierCollection m_trustedCertificateList; private CertificateStoreIdentifier m_issuerCertificateStore; @@ -1613,7 +1796,7 @@ public class CertificateValidationEventArgs : EventArgs /// /// Creates a new instance. /// - internal CertificateValidationEventArgs(ServiceResult error, X509Certificate2 certificate) + public CertificateValidationEventArgs(ServiceResult error, X509Certificate2 certificate) { m_error = error; m_certificate = certificate; @@ -1662,8 +1845,8 @@ public string ApplicationErrorMsg #endregion #region Private Fields - private ServiceResult m_error; - private X509Certificate2 m_certificate; + private readonly ServiceResult m_error; + private readonly X509Certificate2 m_certificate; private bool m_accept; private bool m_acceptAll; private string m_applicationErrorMsg; @@ -1686,7 +1869,7 @@ public class CertificateUpdateEventArgs : EventArgs /// /// Creates a new instance. /// - internal CertificateUpdateEventArgs( + public CertificateUpdateEventArgs( SecurityConfiguration configuration, ICertificateValidator validator) { diff --git a/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs b/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs index e96a23988..3099bede9 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs @@ -931,7 +931,7 @@ private class Entry #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private bool m_noSubDirs; private DirectoryInfo m_directory; private DirectoryInfo m_certificateSubdir; diff --git a/Stack/Opc.Ua.Core/Security/Certificates/ICertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/ICertificateValidator.cs index 3219da61c..256350525 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/ICertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/ICertificateValidator.cs @@ -11,6 +11,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; namespace Opc.Ua { @@ -29,5 +31,14 @@ public interface ICertificateValidator /// void Validate(X509Certificate2Collection certificateChain); + /// + /// Validates a certificate. + /// + Task ValidateAsync(X509Certificate2 certificate, CancellationToken ct); + + /// + /// Validates a certificate chain. + /// + Task ValidateAsync(X509Certificate2Collection certificateChain, CancellationToken ct); } } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/RsaUtils.cs b/Stack/Opc.Ua.Core/Security/Certificates/RsaUtils.cs index 68fb7d708..03d2550b9 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/RsaUtils.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/RsaUtils.cs @@ -330,8 +330,8 @@ internal static bool TryVerifyRSAPssSign(RSA publicKey, RSA privateKey) int blockSize = 0x10; byte[] testBlock = new byte[blockSize]; randomSource.NextBytes(testBlock, 0, blockSize); - byte[] signature = privateKey.SignData(testBlock, HashAlgorithmName.SHA1, RSASignaturePadding.Pss); - return publicKey.VerifyData(testBlock, signature, HashAlgorithmName.SHA1, RSASignaturePadding.Pss); + byte[] signature = privateKey.SignData(testBlock, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + return publicKey.VerifyData(testBlock, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); } catch { diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs index 7feb23fcf..13121065f 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs @@ -17,6 +17,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Threading; using System.Threading.Tasks; using Opc.Ua.Security.Certificates; @@ -561,6 +562,43 @@ public static X509Certificate2 AddToStore( return certificate; } + /// e + /// Extension to add a certificate to a . + /// + /// + /// Saves also the private key, if available. + /// If written to a Pfx file, the password is used for protection. + /// + /// The certificate to store. + /// Type of certificate store (Directory) . + /// The store path (syntax depends on storeType). + /// The password to use to protect the certificate. + /// The cancellation token. + public static async Task AddToStoreAsync( + this X509Certificate2 certificate, + string storeType, + string storePath, + string password = null, + CancellationToken ct = default) + { + // add cert to the store. + if (!String.IsNullOrEmpty(storePath) && !String.IsNullOrEmpty(storeType)) + { + using (ICertificateStore store = Opc.Ua.CertificateStoreIdentifier.CreateStore(storeType)) + { + if (store == null) + { + throw new ArgumentException("Invalid store type"); + } + + store.Open(storePath, false); + await store.Add(certificate, password).ConfigureAwait(false); + store.Close(); + } + } + return certificate; + } + /// /// Get the hash algorithm from the hash size in bits. /// diff --git a/Stack/Opc.Ua.Core/Security/Constants/SecurityConstants.cs b/Stack/Opc.Ua.Core/Security/Constants/SecurityConstants.cs index 21e3de76f..52e380a9d 100644 --- a/Stack/Opc.Ua.Core/Security/Constants/SecurityConstants.cs +++ b/Stack/Opc.Ua.Core/Security/Constants/SecurityConstants.cs @@ -11,7 +11,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ namespace Opc.Ua -{ +{ /// /// Defines constants for key security policies. /// @@ -46,12 +46,12 @@ public static class SecurityAlgorithms /// The AES128 algorithm used to encrypt data. /// public const string Aes128 = "http://www.w3.org/2001/04/xmlenc#aes128-cbc"; - + /// /// The AES256 algorithm used to encrypt data. /// public const string Aes256 = "http://www.w3.org/2001/04/xmlenc#aes256-cbc"; - + /// /// The RSA-OAEP algorithm used to encrypt data. /// @@ -76,7 +76,7 @@ public static class SecurityAlgorithms /// The RSA-OAEP algorithm used to encrypt keys. /// public const string KwRsaOaep = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"; - + /// /// The RSA-PKCSv1.5 algorithm used to encrypt keys. /// diff --git a/Stack/Opc.Ua.Core/Stack/Bindings/ArraySegmentStream.cs b/Stack/Opc.Ua.Core/Stack/Bindings/ArraySegmentStream.cs index 86b39a118..93d61973e 100644 --- a/Stack/Opc.Ua.Core/Stack/Bindings/ArraySegmentStream.cs +++ b/Stack/Opc.Ua.Core/Stack/Bindings/ArraySegmentStream.cs @@ -225,7 +225,10 @@ public override long Seek(long offset, SeekOrigin origin) int position = (int)offset; - CheckEndOfStream(); + if (position >= GetAbsolutePosition()) + { + CheckEndOfStream(); + } for (int ii = 0; ii < m_buffers.Count; ii++) { diff --git a/Stack/Opc.Ua.Core/Stack/Bindings/BaseBinding.cs b/Stack/Opc.Ua.Core/Stack/Bindings/BaseBinding.cs index 8594827a4..12b0d9e97 100644 --- a/Stack/Opc.Ua.Core/Stack/Bindings/BaseBinding.cs +++ b/Stack/Opc.Ua.Core/Stack/Bindings/BaseBinding.cs @@ -15,7 +15,7 @@ namespace Opc.Ua.Bindings /// /// The binding for the UA native stack /// - public abstract class BaseBinding + public abstract class BaseBinding { #region Constructors /// diff --git a/Stack/Opc.Ua.Core/Stack/Bindings/BufferManager.cs b/Stack/Opc.Ua.Core/Stack/Bindings/BufferManager.cs index f6b74ff6f..1e3a50502 100644 --- a/Stack/Opc.Ua.Core/Stack/Bindings/BufferManager.cs +++ b/Stack/Opc.Ua.Core/Stack/Bindings/BufferManager.cs @@ -123,7 +123,7 @@ public BufferManager(string name, int maxBufferSize) m_name = name; m_arrayPool = maxBufferSize <= 1024 * 1024 ? ArrayPool.Shared - : ArrayPool.Create(maxBufferSize + m_cookieLength, 4); + : ArrayPool.Create(maxBufferSize + kCookieLength, 4); m_maxBufferSize = maxBufferSize; } #endregion @@ -142,7 +142,7 @@ public byte[] TakeBuffer(int size, string owner) throw new ArgumentOutOfRangeException(nameof(size)); } - byte[] buffer = m_arrayPool.Rent(size + m_cookieLength); + byte[] buffer = m_arrayPool.Rent(size + kCookieLength); #if TRACK_MEMORY lock (m_lock) { @@ -164,7 +164,7 @@ public byte[] TakeBuffer(int size, string owner) #if TRACE_MEMORY Utils.LogTrace("{0:X}:TakeBuffer({1:X},{2:X},{3},{4})", this.GetHashCode(), buffer.GetHashCode(), buffer.Length, owner, ++m_buffersTaken); #endif - buffer[buffer.Length - 1] = m_cookieUnlocked; + buffer[buffer.Length - 1] = kCookieUnlocked; return buffer; } @@ -214,14 +214,14 @@ public void TransferBuffer(byte[] buffer, string owner) /// The buffer. public static void LockBuffer(byte[] buffer) { - if (buffer[buffer.Length - 1] != m_cookieUnlocked) + if (buffer[buffer.Length - 1] != kCookieUnlocked) { throw new InvalidOperationException("Buffer is already locked."); } #if TRACE_MEMORY Utils.LogTrace("LockBuffer({0:X},{1:X})", buffer.GetHashCode(), buffer.Length); #endif - buffer[buffer.Length - 1] = m_cookieLocked; + buffer[buffer.Length - 1] = kCookieLocked; } /// @@ -230,14 +230,14 @@ public static void LockBuffer(byte[] buffer) /// The buffer. public static void UnlockBuffer(byte[] buffer) { - if (buffer[buffer.Length - 1] != m_cookieLocked) + if (buffer[buffer.Length - 1] != kCookieLocked) { throw new InvalidOperationException("Buffer is not locked."); } #if TRACE_MEMORY Utils.LogTrace("UnlockBuffer({0:X},{1:X})", buffer.GetHashCode(), buffer.Length); #endif - buffer[buffer.Length - 1] = m_cookieUnlocked; + buffer[buffer.Length - 1] = kCookieUnlocked; } /// @@ -255,13 +255,13 @@ public void ReturnBuffer(byte[] buffer, string owner) #if TRACE_MEMORY Utils.LogTrace("{0:X}:ReturnBuffer({1:X},{2:X},{3},{4})", this.GetHashCode(), buffer.GetHashCode(), buffer.Length, owner, --m_buffersTaken); #endif - if (buffer[buffer.Length - 1] != m_cookieUnlocked) + if (buffer[buffer.Length - 1] != kCookieUnlocked) { throw new InvalidOperationException("Buffer has been locked."); } // destroy cookie - buffer[buffer.Length - 1] = m_cookieUnlocked ^ m_cookieLocked; + buffer[buffer.Length - 1] = kCookieUnlocked ^ kCookieLocked; #if TRACK_MEMORY lock (m_lock) @@ -344,11 +344,11 @@ public void ReturnBuffer(byte[] buffer, string owner) #if TRACE_MEMORY private int m_buffersTaken = 0; #endif - private ArrayPool m_arrayPool; - const byte m_cookieLocked = 0xa5; - const byte m_cookieUnlocked = 0x5a; + private readonly ArrayPool m_arrayPool; + private const byte kCookieLocked = 0xa5; + private const byte kCookieUnlocked = 0x5a; #if TRACK_MEMORY - const byte m_cookieLength = 5; + const byte kCookieLength = 5; class Allocation { public int Id; @@ -363,7 +363,7 @@ class Allocation private int m_id; private SortedDictionary m_allocations = new SortedDictionary(); #else - const byte m_cookieLength = 1; + private const byte kCookieLength = 1; #endif #endregion } diff --git a/Stack/Opc.Ua.Core/Stack/Bindings/TransportBindingsBase.cs b/Stack/Opc.Ua.Core/Stack/Bindings/TransportBindingsBase.cs index 774364f42..11799eb03 100644 --- a/Stack/Opc.Ua.Core/Stack/Bindings/TransportBindingsBase.cs +++ b/Stack/Opc.Ua.Core/Stack/Bindings/TransportBindingsBase.cs @@ -61,7 +61,7 @@ public T GetBinding(string uriScheme) TryAddDefaultTransportBindings(uriScheme); if (!Bindings.TryGetValue(uriScheme, out binding)) { - return default(T); + return default; } } return binding; @@ -97,8 +97,7 @@ public IEnumerable AddBindings(IEnumerable bindings) var result = new List(); foreach (Type bindingType in bindings) { - var binding = Activator.CreateInstance(bindingType) as T; - if (binding != null) + if (Activator.CreateInstance(bindingType) is T binding) { Bindings[binding.UriScheme] = binding; result.Add(bindingType); @@ -126,8 +125,7 @@ protected static bool IsBindingType(System.Type bindingType) return false; } - var listener = Activator.CreateInstance(bindingType) as T; - if (listener == null) + if (!(Activator.CreateInstance(bindingType) is T listener)) { return false; } diff --git a/Stack/Opc.Ua.Core/Stack/Client/ClientBase.cs b/Stack/Opc.Ua.Core/Stack/Client/ClientBase.cs index 48c349e85..b5aec6549 100644 --- a/Stack/Opc.Ua.Core/Stack/Client/ClientBase.cs +++ b/Stack/Opc.Ua.Core/Stack/Client/ClientBase.cs @@ -13,13 +13,14 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System; using System.Collections; using System.Threading; +using System.Threading.Tasks; namespace Opc.Ua { /// /// The client side interface with a UA server. /// - public partial class ClientBase : IClientBase, IDisposable + public partial class ClientBase : IClientBase { #region Constructors /// @@ -274,6 +275,21 @@ public virtual StatusCode Close() return StatusCodes.Good; } + /// + /// Closes the channel using async call. + /// + public async virtual Task CloseAsync(CancellationToken ct = default) + { + if (m_channel != null) + { + await m_channel.CloseAsync(ct).ConfigureAwait(false); + m_channel = null; + } + + m_authenticationToken = null; + return StatusCodes.Good; + } + /// /// Whether the object has been disposed. /// @@ -305,9 +321,8 @@ protected void InitializeChannel(ITransportChannel channel) m_channel = channel; m_useTransportChannel = true; - UaChannelBase uaChannel = channel as UaChannelBase; - if (uaChannel != null) + if (channel is UaChannelBase uaChannel) { m_useTransportChannel = uaChannel.m_uaBypassChannel != null || uaChannel.UseBinaryEncoding; } @@ -603,7 +618,7 @@ public static ServiceResult ValidateDataValue( #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private ITransportChannel m_channel; private NodeId m_authenticationToken; private DiagnosticsMasks m_returnDiagnostics; diff --git a/Stack/Opc.Ua.Core/Stack/Client/DiscoveryClient.cs b/Stack/Opc.Ua.Core/Stack/Client/DiscoveryClient.cs index d81e89ca2..1cc658329 100644 --- a/Stack/Opc.Ua.Core/Stack/Client/DiscoveryClient.cs +++ b/Stack/Opc.Ua.Core/Stack/Client/DiscoveryClient.cs @@ -162,6 +162,7 @@ public virtual EndpointDescriptionCollection GetEndpoints(StringCollection profi return PatchEndpointUrls(endpoints); } +#if NET_STANDARD_ASYNC /// /// Invokes the GetEndpoints service async. /// @@ -172,6 +173,7 @@ public async virtual Task GetEndpointsAsync(Strin var response = await GetEndpointsAsync(null, this.Endpoint.EndpointUrl, null, profileUris, ct).ConfigureAwait(false); return PatchEndpointUrls(response.Endpoints); } +#endif /// /// Invokes the FindServers service. @@ -192,6 +194,7 @@ public virtual ApplicationDescriptionCollection FindServers(StringCollection ser return servers; } +#if NET_STANDARD_ASYNC /// /// Invokes the FindServers service async. /// @@ -208,6 +211,7 @@ public virtual async Task FindServersAsync(Str ct).ConfigureAwait(false); return response.Servers; } +#endif /// /// Invokes the FindServersOnNetwork service. diff --git a/Stack/Opc.Ua.Core/Stack/Client/IServiceRequest.cs b/Stack/Opc.Ua.Core/Stack/Client/IServiceRequest.cs index 794f890eb..ea00620c0 100644 --- a/Stack/Opc.Ua.Core/Stack/Client/IServiceRequest.cs +++ b/Stack/Opc.Ua.Core/Stack/Client/IServiceRequest.cs @@ -11,10 +11,10 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ namespace Opc.Ua -{ +{ /// - /// An interface to a service request. - /// + /// An interface to a service request. + /// public interface IServiceRequest : IEncodeable { /// @@ -23,11 +23,11 @@ public interface IServiceRequest : IEncodeable /// The request header. RequestHeader RequestHeader { get; set; } } - + /// - /// An interface to a service response. - /// - public interface IServiceResponse : IEncodeable + /// An interface to a service response. + /// + public interface IServiceResponse : IEncodeable { /// /// The header for the response. @@ -35,10 +35,10 @@ public interface IServiceResponse : IEncodeable /// The response header. ResponseHeader ResponseHeader { get; } } - + /// - /// An interface to a service message. - /// + /// An interface to a service message. + /// public interface IServiceMessage { /// @@ -46,7 +46,7 @@ public interface IServiceMessage /// /// IServiceRequest GetRequest(); - + /// /// Creates an instance of a response message. /// diff --git a/Stack/Opc.Ua.Core/Stack/Client/IUserIdentity.cs b/Stack/Opc.Ua.Core/Stack/Client/IUserIdentity.cs index c93013d02..36350e158 100644 --- a/Stack/Opc.Ua.Core/Stack/Client/IUserIdentity.cs +++ b/Stack/Opc.Ua.Core/Stack/Client/IUserIdentity.cs @@ -42,7 +42,7 @@ public interface IUserIdentity /// /// The type of the issued token. XmlQualifiedName IssuedTokenType { get; } - + /// /// Whether the object can create signatures to prove possession of the user's credentials. /// diff --git a/Stack/Opc.Ua.Core/Stack/Client/RegistrationClient.cs b/Stack/Opc.Ua.Core/Stack/Client/RegistrationClient.cs index 9278d9fed..cfb241d59 100644 --- a/Stack/Opc.Ua.Core/Stack/Client/RegistrationClient.cs +++ b/Stack/Opc.Ua.Core/Stack/Client/RegistrationClient.cs @@ -30,19 +30,19 @@ public partial class RegistrationClient /// The endpoint configuration. /// The instance certificate. /// - public static RegistrationClient Create( + public static RegistrationClient Create( ApplicationConfiguration configuration, - EndpointDescription description, - EndpointConfiguration endpointConfiguration, - X509Certificate2 instanceCertificate) + EndpointDescription description, + EndpointConfiguration endpointConfiguration, + X509Certificate2 instanceCertificate) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (description == null) throw new ArgumentNullException(nameof(description)); ITransportChannel channel = RegistrationChannel.Create( - configuration, - description, - endpointConfiguration, + configuration, + description, + endpointConfiguration, instanceCertificate, new ServiceMessageContext()); @@ -51,7 +51,7 @@ public static RegistrationClient Create( #endregion } - + /// /// A channel object used by clients to access a UA discovery service. /// @@ -99,5 +99,5 @@ public static ITransportChannel Create( } #endregion - } + } } diff --git a/Stack/Opc.Ua.Core/Stack/Client/SessionChannel.cs b/Stack/Opc.Ua.Core/Stack/Client/SessionChannel.cs index 9187dbec2..39c1fb6ef 100644 --- a/Stack/Opc.Ua.Core/Stack/Client/SessionChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Client/SessionChannel.cs @@ -17,7 +17,7 @@ namespace Opc.Ua /// /// A channel object used by clients to access a UA service. /// - public partial class SessionChannel + public partial class SessionChannel { #region Constructors /// diff --git a/Stack/Opc.Ua.Core/Stack/Client/SessionClient.cs b/Stack/Opc.Ua.Core/Stack/Client/SessionClient.cs index 95a84a360..135d71261 100644 --- a/Stack/Opc.Ua.Core/Stack/Client/SessionClient.cs +++ b/Stack/Opc.Ua.Core/Stack/Client/SessionClient.cs @@ -76,7 +76,7 @@ public virtual void SessionCreated(NodeId sessionId, NodeId sessionCookie) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private NodeId m_sessionId; #endregion } diff --git a/Stack/Opc.Ua.Core/Stack/Client/UaChannelBase.cs b/Stack/Opc.Ua.Core/Stack/Client/UaChannelBase.cs index bb7ba59ee..1442a4738 100644 --- a/Stack/Opc.Ua.Core/Stack/Client/UaChannelBase.cs +++ b/Stack/Opc.Ua.Core/Stack/Client/UaChannelBase.cs @@ -356,6 +356,20 @@ public void Close() CloseChannel(); } + /// + /// Closes any existing secure channel. + /// + public async Task CloseAsync(CancellationToken ct) + { + if (m_uaBypassChannel != null) + { + await m_uaBypassChannel.CloseAsync(ct).ConfigureAwait(false); + return; + } + + CloseChannel(); + } + /// /// Begins an asynchronous operation to close the secure channel. /// @@ -439,6 +453,19 @@ public IServiceResponse EndSendRequest(IAsyncResult result) #endif } + /// + /// Completes an asynchronous operation to send a request over the secure channel. + /// + public Task EndSendRequestAsync(IAsyncResult result, CancellationToken ct) + { + if (m_uaBypassChannel != null) + { + return m_uaBypassChannel.EndSendRequestAsync(result, ct); + } + + throw new NotImplementedException(); + } + /// /// Sends a request over the secure channel. /// @@ -978,9 +1005,7 @@ public void OnOperationCompleted(IAsyncResult ar) /// The oject that public static new UaChannelAsyncResult WaitForComplete(IAsyncResult ar) { - UaChannelAsyncResult asyncResult = ar as UaChannelAsyncResult; - - if (asyncResult == null) + if (!(ar is UaChannelAsyncResult asyncResult)) { throw new ArgumentException("End called with an invalid IAsyncResult object.", nameof(ar)); } diff --git a/Stack/Opc.Ua.Core/Stack/Client/UserIdentity.cs b/Stack/Opc.Ua.Core/Stack/Client/UserIdentity.cs index 1d6913a59..6f21c5181 100644 --- a/Stack/Opc.Ua.Core/Stack/Client/UserIdentity.cs +++ b/Stack/Opc.Ua.Core/Stack/Client/UserIdentity.cs @@ -183,8 +183,7 @@ private void Initialize(UserIdentityToken token) m_grantedRoleIds = new NodeIdCollection(); m_token = token; - UserNameIdentityToken usernameToken = token as UserNameIdentityToken; - if (usernameToken != null) + if (token is UserNameIdentityToken usernameToken) { m_tokenType = UserTokenType.UserName; m_issuedTokenType = null; @@ -192,8 +191,7 @@ private void Initialize(UserIdentityToken token) return; } - X509IdentityToken x509Token = token as X509IdentityToken; - if (x509Token != null) + if (token is X509IdentityToken x509Token) { m_tokenType = UserTokenType.Certificate; m_issuedTokenType = null; @@ -209,8 +207,7 @@ private void Initialize(UserIdentityToken token) return; } - IssuedIdentityToken issuedToken = token as IssuedIdentityToken; - if (issuedToken != null) + if (token is IssuedIdentityToken issuedToken) { if (issuedToken.IssuedTokenType == Ua.IssuedTokenType.JWT) { @@ -230,8 +227,7 @@ private void Initialize(UserIdentityToken token) } } - AnonymousIdentityToken anonymousToken = token as AnonymousIdentityToken; - if (anonymousToken != null) + if (token is AnonymousIdentityToken anonymousToken) { m_tokenType = UserTokenType.Anonymous; m_issuedTokenType = null; diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs b/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs index 423bf99e7..416e2883a 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs @@ -439,8 +439,7 @@ public virtual async Task Validate(ApplicationType applicationType) // load private key await SecurityConfiguration.ApplicationCertificate.LoadPrivateKeyEx(SecurityConfiguration.CertificatePasswordProvider).ConfigureAwait(false); - Func generateDefaultUri = () => - { + Func generateDefaultUri = () => { var sb = new StringBuilder(); sb.Append("urn:"); sb.Append(Utils.GetHostName()); diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/ConfigurationWatcher.cs b/Stack/Opc.Ua.Core/Stack/Configuration/ConfigurationWatcher.cs index 09e875ecd..7bf3c3458 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/ConfigurationWatcher.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/ConfigurationWatcher.cs @@ -14,7 +14,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.IO; namespace Opc.Ua -{ +{ /// /// Watches the configuration file and reports any changes. /// @@ -40,13 +40,13 @@ public ConfigurationWatcher(ApplicationConfiguration configuration) m_watcher = new System.Threading.Timer(Watcher_Changed, null, 5000, 5000); } #endregion - + #region IDisposable Members /// /// Frees any unmanaged resources. /// public void Dispose() - { + { Dispose(true); GC.SuppressFinalize(this); } @@ -56,7 +56,7 @@ public void Dispose() /// protected virtual void Dispose(bool disposing) { - if (disposing) + if (disposing) { if (m_watcher != null) { @@ -90,7 +90,7 @@ public event EventHandler Changed } } #endregion - + #region Private Methods /// /// Handles a file changed event. @@ -128,14 +128,14 @@ private void Watcher_Changed(object state) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private ApplicationConfiguration m_configuration; private System.Threading.Timer m_watcher; private DateTime m_lastWriteTime; private event EventHandler m_Changed; #endregion } - + #region ConfigurationWatcherEventArgs Class /// /// Stores the arguments passed when the configuration file changes. @@ -154,7 +154,7 @@ public ConfigurationWatcherEventArgs( m_filePath = filePath; } #endregion - + #region Public Properties /// /// The application configuration which changed. @@ -163,7 +163,7 @@ public ApplicationConfiguration Configuration { get { return m_configuration; } } - + /// /// The path to the application configuration file. /// diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs b/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs index 654ef40f2..32eefd215 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs @@ -1110,6 +1110,7 @@ public void UpdateFromServer( } } +#if NET_STANDARD_ASYNC /// /// Updates an endpoint with information from the server's discovery endpoint. /// @@ -1175,9 +1176,10 @@ public async Task UpdateFromServerAsync( } finally { - client.Close(); + await client.CloseAsync(ct).ConfigureAwait(false); } } +#endif /// /// Returns a discovery url that can be used to update the endpoint description. @@ -1207,7 +1209,7 @@ public Uri GetDiscoveryUrl(Uri endpointUrl) { if (endpointUrl.Scheme.StartsWith(Utils.UriSchemeHttp, StringComparison.Ordinal)) { - return new Uri(String.Format(CultureInfo.InvariantCulture, "{0}"+ kDiscoverySuffix, endpointUrl)); + return new Uri(String.Format(CultureInfo.InvariantCulture, "{0}" + kDiscoverySuffix, endpointUrl)); } else { diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/EndpointConfiguration.cs b/Stack/Opc.Ua.Core/Stack/Configuration/EndpointConfiguration.cs index 84f01dd4e..d574b660f 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/EndpointConfiguration.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/EndpointConfiguration.cs @@ -28,14 +28,14 @@ public static EndpointConfiguration Create() { EndpointConfiguration configuration = new EndpointConfiguration(); - configuration.OperationTimeout = 120000; - configuration.UseBinaryEncoding = true; - configuration.MaxArrayLength = UInt16.MaxValue; - configuration.MaxByteStringLength = UInt16.MaxValue*16; - configuration.MaxMessageSize = UInt16.MaxValue*64; - configuration.MaxStringLength = UInt16.MaxValue; - configuration.MaxBufferSize = UInt16.MaxValue; - configuration.ChannelLifetime = 120000; + configuration.OperationTimeout = 120000; + configuration.UseBinaryEncoding = true; + configuration.MaxArrayLength = UInt16.MaxValue; + configuration.MaxByteStringLength = UInt16.MaxValue * 16; + configuration.MaxMessageSize = UInt16.MaxValue * 64; + configuration.MaxStringLength = UInt16.MaxValue; + configuration.MaxBufferSize = UInt16.MaxValue; + configuration.ChannelLifetime = 120000; configuration.SecurityTokenLifetime = 3600000; return configuration; @@ -52,16 +52,16 @@ public static EndpointConfiguration Create(ApplicationConfiguration applicationC } EndpointConfiguration configuration = new EndpointConfiguration(); - - configuration.OperationTimeout = applicationConfiguration.TransportQuotas.OperationTimeout; - configuration.UseBinaryEncoding = true; - configuration.MaxArrayLength = applicationConfiguration.TransportQuotas.MaxArrayLength; - configuration.MaxByteStringLength = applicationConfiguration.TransportQuotas.MaxByteStringLength; - configuration.MaxMessageSize = applicationConfiguration.TransportQuotas.MaxMessageSize; - configuration.MaxStringLength = applicationConfiguration.TransportQuotas.MaxStringLength; - configuration.MaxBufferSize = applicationConfiguration.TransportQuotas.MaxBufferSize; - configuration.ChannelLifetime = applicationConfiguration.TransportQuotas.ChannelLifetime; - configuration.SecurityTokenLifetime = applicationConfiguration.TransportQuotas.SecurityTokenLifetime; + + configuration.OperationTimeout = applicationConfiguration.TransportQuotas.OperationTimeout; + configuration.UseBinaryEncoding = true; + configuration.MaxArrayLength = applicationConfiguration.TransportQuotas.MaxArrayLength; + configuration.MaxByteStringLength = applicationConfiguration.TransportQuotas.MaxByteStringLength; + configuration.MaxMessageSize = applicationConfiguration.TransportQuotas.MaxMessageSize; + configuration.MaxStringLength = applicationConfiguration.TransportQuotas.MaxStringLength; + configuration.MaxBufferSize = applicationConfiguration.TransportQuotas.MaxBufferSize; + configuration.ChannelLifetime = applicationConfiguration.TransportQuotas.ChannelLifetime; + configuration.SecurityTokenLifetime = applicationConfiguration.TransportQuotas.SecurityTokenLifetime; return configuration; } diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/ISecurityConfigurationManager.cs b/Stack/Opc.Ua.Core/Stack/Configuration/ISecurityConfigurationManager.cs index b480becb5..b9154ad0a 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/ISecurityConfigurationManager.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/ISecurityConfigurationManager.cs @@ -63,9 +63,8 @@ public static ISecurityConfigurationManager CreateInstance(string typeName) typeName); } - ISecurityConfigurationManager configuration = Activator.CreateInstance(type) as ISecurityConfigurationManager; - if (configuration == null) + if (!(Activator.CreateInstance(type) is ISecurityConfigurationManager configuration)) { throw ServiceResultException.Create( StatusCodes.BadNotSupported, diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/ServerProperties.cs b/Stack/Opc.Ua.Core/Stack/Configuration/ServerProperties.cs index 7940ee385..d080a6abc 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/ServerProperties.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/ServerProperties.cs @@ -18,86 +18,86 @@ namespace Opc.Ua /// The properties of the current server instance. /// public class ServerProperties - { -#region Constructors + { + #region Constructors /// /// The default constructor. /// public ServerProperties() - { - m_productUri = String.Empty; - m_manufacturerName = String.Empty; - m_productName = String.Empty; - m_softwareVersion = String.Empty; - m_buildNumber = String.Empty; - m_buildDate = DateTime.MinValue; + { + m_productUri = String.Empty; + m_manufacturerName = String.Empty; + m_productName = String.Empty; + m_softwareVersion = String.Empty; + m_buildNumber = String.Empty; + m_buildDate = DateTime.MinValue; m_datatypeAssemblies = new StringCollection(); m_softwareCertificates = new SignedSoftwareCertificateCollection(); - } -#endregion + } + #endregion + + #region Public Properties + /// + /// The unique identifier for the product. + /// + public string ProductUri + { + get { return m_productUri; } + set { m_productUri = value; } + } + + /// + /// The name of the product + /// + public string ProductName + { + get { return m_productName; } + set { m_productName = value; } + } -#region Public Properties - /// - /// The unique identifier for the product. - /// - public string ProductUri - { - get { return m_productUri; } - set { m_productUri = value; } - } - - /// - /// The name of the product - /// - public string ProductName - { - get { return m_productName; } - set { m_productName = value; } - } - - /// - /// The name of the manufacturer - /// - public string ManufacturerName - { - get { return m_manufacturerName; } - set { m_manufacturerName = value; } - } - - /// - /// The software version for the application - /// - public string SoftwareVersion - { - get { return m_softwareVersion; } - set { m_softwareVersion = value; } - } - - /// - /// The build number for the application - /// - public string BuildNumber - { - get { return m_buildNumber; } - set { m_buildNumber = value; } - } - - /// - /// When the application was built. + /// + /// The name of the manufacturer + /// + public string ManufacturerName + { + get { return m_manufacturerName; } + set { m_manufacturerName = value; } + } + + /// + /// The software version for the application /// - public DateTime BuildDate - { - get { return m_buildDate; } - set { m_buildDate = value; } - } - - /// - /// The assemblies that contain encodeable types that could be uses a variable values. - /// - public StringCollection DatatypeAssemblies - { - get { return m_datatypeAssemblies; } - } + public string SoftwareVersion + { + get { return m_softwareVersion; } + set { m_softwareVersion = value; } + } + + /// + /// The build number for the application + /// + public string BuildNumber + { + get { return m_buildNumber; } + set { m_buildNumber = value; } + } + + /// + /// When the application was built. + /// + public DateTime BuildDate + { + get { return m_buildDate; } + set { m_buildDate = value; } + } + + /// + /// The assemblies that contain encodeable types that could be uses a variable values. + /// + public StringCollection DatatypeAssemblies + { + get { return m_datatypeAssemblies; } + } /// /// The software certificates granted to the server. @@ -106,17 +106,17 @@ public SignedSoftwareCertificateCollection SoftwareCertificates { get { return m_softwareCertificates; } } -#endregion + #endregion -#region Private Members - private string m_productUri; - private string m_productName; - private string m_manufacturerName; - private string m_softwareVersion; - private string m_buildNumber; - private DateTime m_buildDate; + #region Private Members + private string m_productUri; + private string m_productName; + private string m_manufacturerName; + private string m_softwareVersion; + private string m_buildNumber; + private DateTime m_buildDate; private StringCollection m_datatypeAssemblies; private SignedSoftwareCertificateCollection m_softwareCertificates; -#endregion - } + #endregion + } } diff --git a/Stack/Opc.Ua.Core/Stack/Constants/ConditionStateNames.cs b/Stack/Opc.Ua.Core/Stack/Constants/ConditionStateNames.cs index b2200b926..bb18c13e7 100644 --- a/Stack/Opc.Ua.Core/Stack/Constants/ConditionStateNames.cs +++ b/Stack/Opc.Ua.Core/Stack/Constants/ConditionStateNames.cs @@ -19,74 +19,74 @@ namespace Opc.Ua /// public static partial class ConditionStateNames { - /// - /// The name of the Disabled state. - /// + /// + /// The name of the Disabled state. + /// public const string Disabled = "Disabled"; - /// - /// The name of the Enabled state. - /// + /// + /// The name of the Enabled state. + /// public const string Enabled = "Enabled"; - /// - /// The name of the Inactive state. - /// + /// + /// The name of the Inactive state. + /// public const string Inactive = "Inactive"; - /// - /// The name of the Active state. - /// + /// + /// The name of the Active state. + /// public const string Active = "Active"; - /// - /// The name of the Unacknowledged state. - /// + /// + /// The name of the Unacknowledged state. + /// public const string Unacknowledged = "Unacknowledged"; - /// - /// The name of the Acknowledged state. - /// + /// + /// The name of the Acknowledged state. + /// public const string Acknowledged = "Acknowledged"; - /// - /// The name of the Unconfirmed state. - /// + /// + /// The name of the Unconfirmed state. + /// public const string Unconfirmed = "Unconfirmed"; - /// - /// The name of the Confirmed state. - /// + /// + /// The name of the Confirmed state. + /// public const string Confirmed = "Confirmed"; - /// - /// The name of the Unsuppressed state. - /// + /// + /// The name of the Unsuppressed state. + /// public const string Unsuppressed = "Unsuppressed"; - /// - /// The name of the Suppressed state. - /// + /// + /// The name of the Suppressed state. + /// public const string Suppressed = "Suppressed"; - /// - /// The name of the HighHighActive state. - /// + /// + /// The name of the HighHighActive state. + /// public const string HighHighActive = "HighHighActive"; - /// - /// The name of the HighActive state. - /// + /// + /// The name of the HighActive state. + /// public const string HighActive = "HighActive"; - /// - /// The name of the LowActive state. - /// + /// + /// The name of the LowActive state. + /// public const string LowActive = "LowActive"; - /// - /// The name of the LowLowActive state. - /// + /// + /// The name of the LowLowActive state. + /// public const string LowLowActive = "LowLowActive"; } } diff --git a/Stack/Opc.Ua.Core/Stack/Constants/DataTypes.Helpers.cs b/Stack/Opc.Ua.Core/Stack/Constants/DataTypes.Helpers.cs index 4c84279ee..76191a93b 100644 --- a/Stack/Opc.Ua.Core/Stack/Constants/DataTypes.Helpers.cs +++ b/Stack/Opc.Ua.Core/Stack/Constants/DataTypes.Helpers.cs @@ -16,65 +16,65 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. namespace Opc.Ua { - /// - /// A class that defines constants used by UA applications. - /// - public static partial class DataTypes - { + /// + /// A class that defines constants used by UA applications. + /// + public static partial class DataTypes + { #region Static Helper Functions /// /// Returns the browse name for the attribute. /// public static string GetBrowseName(int identifier) - { - FieldInfo[] fields = typeof(DataTypes).GetFields(BindingFlags.Public | BindingFlags.Static); + { + FieldInfo[] fields = typeof(DataTypes).GetFields(BindingFlags.Public | BindingFlags.Static); - foreach (FieldInfo field in fields) - { + foreach (FieldInfo field in fields) + { if (identifier == (uint)field.GetValue(typeof(DataTypes))) - { - return field.Name; - } - } + { + return field.Name; + } + } - return System.String.Empty; - } + return System.String.Empty; + } + + /// + /// Returns the browse names for all attributes. + /// + public static string[] GetBrowseNames() + { + FieldInfo[] fields = typeof(DataTypes).GetFields(BindingFlags.Public | BindingFlags.Static); - /// - /// Returns the browse names for all attributes. - /// - public static string[] GetBrowseNames() - { - FieldInfo[] fields = typeof(DataTypes).GetFields(BindingFlags.Public | BindingFlags.Static); - int ii = 0; string[] names = new string[fields.Length]; - - foreach (FieldInfo field in fields) - { - names[ii++] = field.Name; - } - return names; - } + foreach (FieldInfo field in fields) + { + names[ii++] = field.Name; + } - /// - /// Returns the id for the attribute with the specified browse name. - /// + return names; + } + + /// + /// Returns the id for the attribute with the specified browse name. + /// public static uint GetIdentifier(string browseName) - { - FieldInfo[] fields = typeof(DataTypes).GetFields(BindingFlags.Public | BindingFlags.Static); + { + FieldInfo[] fields = typeof(DataTypes).GetFields(BindingFlags.Public | BindingFlags.Static); - foreach (FieldInfo field in fields) - { - if (field.Name == browseName) - { + foreach (FieldInfo field in fields) + { + if (field.Name == browseName) + { return (uint)field.GetValue(typeof(DataTypes)); - } - } + } + } - return 0; + return 0; } /// @@ -116,15 +116,15 @@ public static int GetValueRank(Type type) { return TypeInfo.GetValueRank(type); } - + /// /// Returns the BuiltInType type for the DataTypeId. /// public static BuiltInType GetBuiltInType(NodeId datatypeId) { return TypeInfo.GetBuiltInType(datatypeId); - } - + } + /// /// Returns the BuiltInType type for the DataTypeId. /// diff --git a/Stack/Opc.Ua.Core/Stack/Constants/Namespaces.cs b/Stack/Opc.Ua.Core/Stack/Constants/Namespaces.cs index 477e29f06..94255dd42 100644 --- a/Stack/Opc.Ua.Core/Stack/Constants/Namespaces.cs +++ b/Stack/Opc.Ua.Core/Stack/Constants/Namespaces.cs @@ -14,36 +14,36 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. namespace Opc.Ua { - /// - /// Defines well-known namespaces. - /// + /// + /// Defines well-known namespaces. + /// public static partial class Namespaces - { - /// - /// The XML Schema namespace. - /// + { + /// + /// The XML Schema namespace. + /// public const string XmlSchema = "http://www.w3.org/2001/XMLSchema"; - /// - /// The XML Schema Instance namespace. - /// + /// + /// The XML Schema Instance namespace. + /// public const string XmlSchemaInstance = "http://www.w3.org/2001/XMLSchema-instance"; - /// - /// The WS Secuirity Extensions Namespace. - /// + /// + /// The WS Secuirity Extensions Namespace. + /// public const string WSSecurityExtensions = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; - - /// - /// The WS Secuirity Utilities Namespace. - /// + + /// + /// The WS Secuirity Utilities Namespace. + /// public const string WSSecurityUtilities = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"; - - /// - /// The URI for the UA WSDL. - /// + + /// + /// The URI for the UA WSDL. + /// public const string OpcUaWsdl = "http://opcfoundation.org/UA/2008/02/Services.wsdl"; - + /// /// The URI for the UA SecuredApplication schema. /// @@ -53,17 +53,17 @@ public static partial class Namespaces /// The base URI for the Global Discovery Service. /// public const string OpcUaGds = "http://opcfoundation.org/UA/GDS/"; - - /// - /// The base URI for SDK related schemas. - /// + + /// + /// The base URI for SDK related schemas. + /// public const string OpcUaSdk = "http://opcfoundation.org/UA/SDK/"; - /// - /// The URI for the UA SDK Configuration Schema. - /// + /// + /// The URI for the UA SDK Configuration Schema. + /// public const string OpcUaConfig = OpcUaSdk + "Configuration.xsd"; - + /// /// The URI for the built-in types namespace. /// diff --git a/Stack/Opc.Ua.Core/Stack/Nodes/ContentFilter.cs b/Stack/Opc.Ua.Core/Stack/Nodes/ContentFilter.cs index 87827685b..1688bca5a 100644 --- a/Stack/Opc.Ua.Core/Stack/Nodes/ContentFilter.cs +++ b/Stack/Opc.Ua.Core/Stack/Nodes/ContentFilter.cs @@ -17,7 +17,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. namespace Opc.Ua { #region ContentFilter Class - public partial class ContentFilter: IFormattable + public partial class ContentFilter : IFormattable { #region IFormattable Members /// @@ -62,7 +62,7 @@ public override string ToString() return ToString(null, null); } #endregion - + /// /// Validates the ContentFilter. /// @@ -71,24 +71,24 @@ public override string ToString() public Result Validate(FilterContext context) { Result result = new Result(null); - + // check for empty filter. if (m_elements == null || m_elements.Count == 0) { return result; - } + } bool error = false; for (int ii = 0; ii < m_elements.Count; ii++) { ContentFilterElement element = m_elements[ii]; - + // check for null. if (element == null) { ServiceResult nullResult = ServiceResult.Create( - StatusCodes.BadStructureMissing, + StatusCodes.BadStructureMissing, "ContentFilterElement is null (Index={0}).", ii); @@ -96,7 +96,7 @@ public Result Validate(FilterContext context) error = true; continue; } - + element.Parent = this; // validate element. @@ -111,7 +111,7 @@ public Result Validate(FilterContext context) result.ElementResults.Add(null); } - + // ensure the global error code. if (error) { @@ -124,7 +124,7 @@ public Result Validate(FilterContext context) return result; } - + /// /// Pushes a new element onto the stack. /// @@ -132,32 +132,30 @@ public Result Validate(FilterContext context) /// The operands. /// public ContentFilterElement Push(FilterOperator op, params object[] operands) - { + { // check if nothing more to do. if (operands == null || operands.Length == 0) - { + { throw ServiceResultException.Create(StatusCodes.BadInvalidArgument, "ContentFilterElement does not have an operands."); } - + // create the element and set the operator. - ContentFilterElement element = new ContentFilterElement(); + ContentFilterElement element = new ContentFilterElement(); element.FilterOperator = op; - + for (int ii = 0; ii < operands.Length; ii++) { // check if a FilterOperand was provided. - FilterOperand filterOperand = operands[ii] as FilterOperand; - - if (filterOperand != null) + + if (operands[ii] is FilterOperand filterOperand) { element.FilterOperands.Add(new ExtensionObject(filterOperand)); continue; } - + // check for reference to another ContentFilterElement. - ContentFilterElement existingElement = operands[ii] as ContentFilterElement; - if (existingElement != null) + if (operands[ii] is ContentFilterElement existingElement) { int index = FindElementIndex(existingElement); @@ -189,13 +187,11 @@ public ContentFilterElement Push(FilterOperator op, params object[] operands) { if (extension != null) { - ElementOperand operand = extension.Body as ElementOperand; - - if (operand != null) + if (extension.Body is ElementOperand operand) { operand.Index++; } - } + } } } @@ -220,7 +216,7 @@ private int FindElementIndex(ContentFilterElement target) return -1; } - + #region Result Class /// /// Stores the validation results for a ContentFilterElement. @@ -246,17 +242,17 @@ public static implicit operator Result(ServiceResult status) { return new Result(status); } - + /// /// The result for the entire filter. /// /// The status. public ServiceResult Status { - get { return m_status; } + get { return m_status; } set { m_status = value; } } - + /// /// The result for each element. /// @@ -294,7 +290,7 @@ public ContentFilterResult ToContextFilterResult(DiagnosticsMasks diagnosticsMas foreach (ElementResult elementResult in m_elementResults) { ContentFilterElementResult elementResult2 = null; - + if (elementResult == null || ServiceResult.IsGood(elementResult.Status)) { elementResult2 = new ContentFilterElementResult(); @@ -306,7 +302,7 @@ public ContentFilterResult ToContextFilterResult(DiagnosticsMasks diagnosticsMas } error = true; - + elementResult2 = elementResult.ToContentFilterElementResult(diagnosticsMasks, stringTable); result.ElementResults.Add(elementResult2); result.ElementDiagnosticInfos.Add(new DiagnosticInfo(elementResult.Status, diagnosticsMasks, false, stringTable)); @@ -317,18 +313,18 @@ public ContentFilterResult ToContextFilterResult(DiagnosticsMasks diagnosticsMas result.ElementResults.Clear(); result.ElementDiagnosticInfos.Clear(); } - + return result; } #endregion - + #region Private Fields private ServiceResult m_status; private List m_elementResults; #endregion } #endregion - + #region ElementResult Class /// /// Stores the validation results for a ContentFilterElement. @@ -354,17 +350,17 @@ public static implicit operator ElementResult(ServiceResult status) { return new ElementResult(status); } - + /// /// The result for the entire element. /// /// The status. public ServiceResult Status { - get { return m_status; } + get { return m_status; } set { m_status = value; } } - + /// /// The result for each operand. /// @@ -381,7 +377,7 @@ public List OperandResults return m_operandResults; } } - + /// /// Converts the object to an ContentFilterElementResult. /// @@ -397,7 +393,7 @@ public ContentFilterElementResult ToContentFilterElementResult(DiagnosticsMasks result.StatusCode = StatusCodes.Good; return result; } - + result.StatusCode = m_status.StatusCode; if (m_operandResults.Count == 0) @@ -413,17 +409,17 @@ public ContentFilterElementResult ToContentFilterElementResult(DiagnosticsMasks result.OperandDiagnosticInfos.Add(null); } else - { - result.OperandStatusCodes.Add(operandResult.StatusCode); + { + result.OperandStatusCodes.Add(operandResult.StatusCode); result.OperandDiagnosticInfos.Add(new DiagnosticInfo(operandResult, diagnosticsMasks, false, stringTable)); - } + } } return result; } #endregion - + #region Private Fields private ServiceResult m_status; private List m_operandResults; @@ -500,7 +496,7 @@ public ContentFilter Parent { get { return m_parent; } internal set { this.m_parent = value; } - } + } /// /// Validates the content filter element. @@ -569,10 +565,10 @@ public virtual ContentFilter.ElementResult Validate(FilterContext context, int i if (operandCount != -1) { if (operandCount != m_filterOperands.Count) - { + { result.Status = ServiceResult.Create( - StatusCodes.BadEventFilterInvalid, - "ContentFilterElement does not have the correct number of operands (Operator={0} OperandCount={1}).", + StatusCodes.BadEventFilterInvalid, + "ContentFilterElement does not have the correct number of operands (Operator={0} OperandCount={1}).", m_filterOperator, operandCount); @@ -582,10 +578,10 @@ public virtual ContentFilter.ElementResult Validate(FilterContext context, int i else { if (m_filterOperands.Count < 2) - { + { result.Status = ServiceResult.Create( - StatusCodes.BadEventFilterInvalid, - "ContentFilterElement does not have the correct number of operands (Operator={0} OperandCount={1}).", + StatusCodes.BadEventFilterInvalid, + "ContentFilterElement does not have the correct number of operands (Operator={0} OperandCount={1}).", m_filterOperator, m_filterOperands.Count); @@ -601,23 +597,22 @@ public virtual ContentFilter.ElementResult Validate(FilterContext context, int i ServiceResult operandResult = null; ExtensionObject operand = m_filterOperands[ii]; - + // check for null. if (ExtensionObject.IsNull(operand)) { operandResult = ServiceResult.Create( StatusCodes.BadEventFilterInvalid, "The FilterOperand cannot be Null."); - + result.OperandResults.Add(operandResult); error = true; continue; - } - + } + // check that the extension object contains a filter operand. - FilterOperand filterOperand = operand.Body as FilterOperand; - if (filterOperand == null) + if (!(operand.Body is FilterOperand filterOperand)) { operandResult = ServiceResult.Create( StatusCodes.BadEventFilterInvalid, @@ -642,7 +637,7 @@ public virtual ContentFilter.ElementResult Validate(FilterContext context, int i result.OperandResults.Add(null); } - + // ensure the global error code. if (error) { @@ -662,7 +657,7 @@ public virtual ContentFilter.ElementResult Validate(FilterContext context, int i /// The list of operands for the element. public List GetOperands() { - List operands = new List(FilterOperands.Count); + List operands = new List(FilterOperands.Count); foreach (ExtensionObject extension in FilterOperands) { @@ -671,19 +666,18 @@ public List GetOperands() continue; } - FilterOperand operand = extension.Body as FilterOperand; - if (operand == null) + if (!(extension.Body is FilterOperand operand)) { continue; } - + operands.Add(operand); } return operands; } - + /// /// Sets the operands for the element. /// @@ -717,9 +711,9 @@ public virtual string ToString(INodeTable nodeTable) { List operands = GetOperands(); - string operand1 = (operands.Count > 0)?operands[0].ToString(nodeTable):null; - string operand2 = (operands.Count > 1)?operands[1].ToString(nodeTable):null; - string operand3 = (operands.Count > 2)?operands[2].ToString(nodeTable):null; + string operand1 = (operands.Count > 0) ? operands[0].ToString(nodeTable) : null; + string operand2 = (operands.Count > 1) ? operands[1].ToString(nodeTable) : null; + string operand3 = (operands.Count > 2) ? operands[2].ToString(nodeTable) : null; StringBuilder buffer = new StringBuilder(); @@ -733,7 +727,7 @@ public virtual string ToString(INodeTable nodeTable) buffer.AppendFormat("{0} '{1}'", FilterOperator, operand1); break; } - + case FilterOperator.And: case FilterOperator.Equals: case FilterOperator.GreaterThan: @@ -748,19 +742,19 @@ public virtual string ToString(INodeTable nodeTable) buffer.AppendFormat("'{1}' {0} '{2}'", FilterOperator, operand1, operand2); break; } - + case FilterOperator.Between: { buffer.AppendFormat("'{1}' <= '{0}' <= '{2}'", operand1, operand2, operand3); break; } - + case FilterOperator.Cast: { buffer.AppendFormat("({1}){0}", operand1, operand2); break; } - + case FilterOperator.InList: { buffer.AppendFormat("'{0}' in ", operand1); @@ -769,27 +763,25 @@ public virtual string ToString(INodeTable nodeTable) for (int ii = 1; ii < operands.Count; ii++) { buffer.AppendFormat("'{0}'", operands[ii].ToString()); - if (ii < operands.Count-1) + if (ii < operands.Count - 1) { buffer.Append(", "); } } - + buffer.Append('}'); break; } - + case FilterOperator.RelatedTo: { buffer.AppendFormat("'{0}' ", operand1); - + string referenceType = operand2; if (operands.Count > 1) { - LiteralOperand literalOperand = operands[1] as LiteralOperand; - - if (literalOperand != null) + if (operands[1] is LiteralOperand literalOperand) { INode node = nodeTable.Find(literalOperand.Value.Value as NodeId); @@ -799,7 +791,7 @@ public virtual string ToString(INodeTable nodeTable) } } } - + buffer.AppendFormat("{0} '{1}'", referenceType, operand2); if (operand3 != null) @@ -858,7 +850,7 @@ public virtual string ToString(INodeTable nodeTable) return Utils.Format("{0}", this); } #endregion - + #region Private Fields private ContentFilterElement m_parent; #endregion @@ -926,17 +918,17 @@ public AttributeOperand( /// The node identifier. /// The relative path. public AttributeOperand( - FilterContext context, + FilterContext context, ExpandedNodeId nodeId, - RelativePath relativePath) + RelativePath relativePath) { - m_nodeId = ExpandedNodeId.ToNodeId(nodeId, context.NamespaceUris); - m_browsePath = relativePath; + m_nodeId = ExpandedNodeId.ToNodeId(nodeId, context.NamespaceUris); + m_browsePath = relativePath; m_attributeId = Attributes.Value; - m_indexRange = null; - m_alias = null; + m_indexRange = null; + m_alias = null; } - + /// /// Creates an operand that references a component/property of a type. /// @@ -946,20 +938,20 @@ public AttributeOperand( /// The attribute identifier. /// The index range. public AttributeOperand( - FilterContext context, + FilterContext context, ExpandedNodeId typeDefinitionId, - string browsePath, - uint attributeId, - string indexRange) + string browsePath, + uint attributeId, + string indexRange) { - m_nodeId = ExpandedNodeId.ToNodeId(typeDefinitionId, context.NamespaceUris); - m_browsePath = RelativePath.Parse(browsePath, context.TypeTree); + m_nodeId = ExpandedNodeId.ToNodeId(typeDefinitionId, context.NamespaceUris); + m_browsePath = RelativePath.Parse(browsePath, context.TypeTree); m_attributeId = attributeId; - m_indexRange = indexRange; - m_alias = null; + m_indexRange = indexRange; + m_alias = null; } #endregion - + #region IFormattable Members /// /// Formats the value of the current instance using the specified format. @@ -990,7 +982,7 @@ public string ToString(string format, IFormatProvider formatProvider) throw new FormatException(Utils.Format("Invalid format string: '{0}'.", format)); } #endregion - + #region Overridden Methods /// /// Returns a that represents the current . @@ -1043,10 +1035,10 @@ public override ServiceResult Validate(FilterContext context, int index) // verify that the operand refers to a node in the type model. if (!context.TypeTree.IsKnown(m_nodeId)) - { + { return ServiceResult.Create( - StatusCodes.BadTypeDefinitionInvalid, - "AttributeOperand does not have a known TypeDefinitionId ({0}).", + StatusCodes.BadTypeDefinitionInvalid, + "AttributeOperand does not have a known TypeDefinitionId ({0}).", m_nodeId); } @@ -1054,8 +1046,8 @@ public override ServiceResult Validate(FilterContext context, int index) if (!Attributes.IsValid(m_attributeId)) { return ServiceResult.Create( - StatusCodes.BadAttributeIdInvalid, - "AttributeOperand does not specify a valid AttributeId ({0}).", + StatusCodes.BadAttributeIdInvalid, + "AttributeOperand does not specify a valid AttributeId ({0}).", m_attributeId); } @@ -1073,16 +1065,16 @@ public override ServiceResult Validate(FilterContext context, int index) { return ServiceResult.Create( e, - StatusCodes.BadIndexRangeInvalid, - "AttributeOperand does not specify a valid BrowsePath ({0}).", + StatusCodes.BadIndexRangeInvalid, + "AttributeOperand does not specify a valid BrowsePath ({0}).", m_indexRange); } if (m_attributeId != Attributes.Value) { return ServiceResult.Create( - StatusCodes.BadIndexRangeInvalid, - "AttributeOperand specifies an IndexRange for an Attribute other than Value ({0}).", + StatusCodes.BadIndexRangeInvalid, + "AttributeOperand specifies an IndexRange for an Attribute other than Value ({0}).", m_attributeId); } } @@ -1111,7 +1103,7 @@ public override string ToString(INodeTable nodeTable) { buffer.AppendFormat("{0}", NodeId); } - + if (!RelativePath.IsEmpty(BrowsePath)) { buffer.AppendFormat("/{0}", BrowsePath.Format(nodeTable.TypeTree)); @@ -1126,11 +1118,11 @@ public override string ToString(INodeTable nodeTable) { buffer.AppendFormat("- '{0}'", Alias); } - + return buffer.ToString(); } #endregion - + #region Private Fields private bool m_validated; private NumericRange m_parsedIndexRange; @@ -1173,7 +1165,7 @@ public string ToString(string format, IFormatProvider formatProvider) throw new FormatException(Utils.Format("Invalid format string: '{0}'.", format)); } #endregion - + #region Overridden Methods /// /// Returns a that represents the current . @@ -1198,24 +1190,24 @@ public override ServiceResult Validate(FilterContext context, int index) if (index < 0) { return ServiceResult.Create( - StatusCodes.BadFilterOperandInvalid, - "ElementOperand specifies an Index that is less than zero ({0}).", + StatusCodes.BadFilterOperandInvalid, + "ElementOperand specifies an Index that is less than zero ({0}).", index); } if (m_index <= index) { return ServiceResult.Create( - StatusCodes.BadFilterOperandInvalid, - "ElementOperand references an element that precedes it in the ContentFilter.", + StatusCodes.BadFilterOperandInvalid, + "ElementOperand references an element that precedes it in the ContentFilter.", m_index); } if (m_index >= Parent.Parent.Elements.Count) { return ServiceResult.Create( - StatusCodes.BadFilterOperandInvalid, - "ElementOperand references an element that does not exist.", + StatusCodes.BadFilterOperandInvalid, + "ElementOperand references an element that does not exist.", m_index); } @@ -1225,9 +1217,9 @@ public override ServiceResult Validate(FilterContext context, int index) /// /// Converts an ElementOperand to a displayable string. /// - /// The table. + /// The table. /// ElementOperand as a displayable string. - public override string ToString(INodeTable table) + public override string ToString(INodeTable nodeTable) { return Utils.Format("Element[{0}]", Index); } @@ -1269,7 +1261,7 @@ public string ToString(string format, IFormatProvider formatProvider) throw new FormatException(Utils.Format("Invalid format string: '{0}'.", format)); } #endregion - + #region Overridden Methods /// /// Returns a that represents the current . @@ -1294,7 +1286,7 @@ public override ServiceResult Validate(FilterContext context, int index) if (m_value.Value == null) { return ServiceResult.Create( - StatusCodes.BadEventFilterInvalid, + StatusCodes.BadEventFilterInvalid, "LiteralOperand specifies a null Value."); } @@ -1304,20 +1296,20 @@ public override ServiceResult Validate(FilterContext context, int index) /// /// Converts an LiteralOperand to a displayable string. /// - /// The table. + /// The table. /// LiteralOperand as a displayable string. - public override string ToString(INodeTable table) + public override string ToString(INodeTable nodeTable) { ExpandedNodeId nodeId = Value.Value as ExpandedNodeId; - + if (nodeId == null) { - nodeId = Value.Value as NodeId; + nodeId = Value.Value as NodeId; } if (nodeId != null) { - INode node = table.Find(nodeId); + INode node = nodeTable.Find(nodeId); if (node != null) { diff --git a/Stack/Opc.Ua.Core/Stack/Nodes/IFilterTarget.cs b/Stack/Opc.Ua.Core/Stack/Nodes/IFilterTarget.cs index 523f904d8..ad56763ea 100644 --- a/Stack/Opc.Ua.Core/Stack/Nodes/IFilterTarget.cs +++ b/Stack/Opc.Ua.Core/Stack/Nodes/IFilterTarget.cs @@ -21,7 +21,7 @@ namespace Opc.Ua /// ContentFilter must implement this interface. /// public interface IFilterTarget - { + { /// /// Checks whether the target is an instance of the specified type. /// @@ -31,8 +31,8 @@ public interface IFilterTarget /// True if the object is an instance of the specified type. /// bool IsTypeOf( - FilterContext context, - NodeId typeDefinitionId); + FilterContext context, + NodeId typeDefinitionId); /// /// Returns the value of an attribute identified by the operand. @@ -46,11 +46,11 @@ bool IsTypeOf( /// The attribute value. Returns null if the attribute does not exist. /// object GetAttributeValue( - FilterContext context, - NodeId typeDefinitionId, + FilterContext context, + NodeId typeDefinitionId, IList relativePath, - uint attributeId, - NumericRange indexRange); + uint attributeId, + NumericRange indexRange); } /// @@ -114,7 +114,7 @@ object GetRelatedAttributeValue( uint attributeId, NumericRange indexRange); } - + /// /// Provides context information to used when searching the address space. /// @@ -193,8 +193,8 @@ public ITypeTable TypeTree /// The session identifier. public NodeId SessionId { - get - { + get + { if (m_context != null) { return m_context.SessionId; @@ -210,8 +210,8 @@ public NodeId SessionId /// The user identity. public IUserIdentity UserIdentity { - get - { + get + { if (m_context != null) { return m_context.UserIdentity; @@ -227,8 +227,8 @@ public IUserIdentity UserIdentity /// The preferred locales. public IList PreferredLocales { - get - { + get + { if (m_context != null) { return m_context.PreferredLocales; @@ -244,8 +244,8 @@ public IList PreferredLocales /// The diagnostics mask. public DiagnosticsMasks DiagnosticsMask { - get - { + get + { if (m_context != null) { return m_context.DiagnosticsMask; @@ -261,8 +261,8 @@ public DiagnosticsMasks DiagnosticsMask /// The string table. public StringTable StringTable { - get - { + get + { if (m_context != null) { return m_context.StringTable; @@ -278,8 +278,8 @@ public StringTable StringTable /// The operation deadline. public DateTime OperationDeadline { - get - { + get + { if (m_context != null) { return m_context.OperationDeadline; @@ -295,8 +295,8 @@ public DateTime OperationDeadline /// The operation status. public StatusCode OperationStatus { - get - { + get + { if (m_context != null) { return m_context.OperationStatus; @@ -312,8 +312,8 @@ public StatusCode OperationStatus /// The audit entry identifier. public string AuditEntryId { - get - { + get + { if (m_context != null) { return m_context.AuditEntryId; diff --git a/Stack/Opc.Ua.Core/Stack/Nodes/INode.cs b/Stack/Opc.Ua.Core/Stack/Nodes/INode.cs index b12a05f44..c54f56b5a 100644 --- a/Stack/Opc.Ua.Core/Stack/Nodes/INode.cs +++ b/Stack/Opc.Ua.Core/Stack/Nodes/INode.cs @@ -30,7 +30,7 @@ public interface INode /// /// The node class. NodeClass NodeClass { get; } - + /// /// The locale independent browse name. /// @@ -49,7 +49,7 @@ public interface INode /// The type definition identifier. ExpandedNodeId TypeDefinitionId { get; } } - + /// /// An interface to an object that describes a node local to the server. /// @@ -146,7 +146,7 @@ public interface ILocalNode : INode /// The result of Write operation ServiceResult Write(uint attributeId, DataValue value); } - + /// /// An interface to an object that describes an ObjectType node. /// @@ -156,7 +156,7 @@ public interface IObjectType : ILocalNode /// Whether the type is an abstract type. /// bool IsAbstract { get; set; } - } + } /// /// An interface to an object that describes an Object node. diff --git a/Stack/Opc.Ua.Core/Stack/Nodes/Node.cs b/Stack/Opc.Ua.Core/Stack/Nodes/Node.cs index ee62b1907..5f787b491 100644 --- a/Stack/Opc.Ua.Core/Stack/Nodes/Node.cs +++ b/Stack/Opc.Ua.Core/Stack/Nodes/Node.cs @@ -110,9 +110,9 @@ public object Handle /// Returns the string representation of the object. /// /// The format. - /// The provider. + /// The provider. /// String representation of the object. - public string ToString(string format, IFormatProvider provider) + public string ToString(string format, IFormatProvider formatProvider) { if (format == null) { @@ -401,6 +401,16 @@ public ExpandedNodeId GetSuperType(ITypeTable typeTree) return null; } + + /// + public override int GetHashCode() + { + HashCode hash = new HashCode(); + hash.Add(m_nodeId); + hash.Add(m_nodeClass); + hash.Add(m_browseName); + return hash.ToHashCode(); + } #endregion #region Protected Methods @@ -501,9 +511,9 @@ public override string ToString() /// Returns a string representation of the HierarchyBrowsePath. /// /// The format. - /// The provider. + /// The provider. /// A string representation of the HierarchyBrowsePath. - public string ToString(string format, IFormatProvider provider) + public string ToString(string format, IFormatProvider formatProvider) { if (format != null) { @@ -557,7 +567,11 @@ public override bool Equals(object obj) /// public override int GetHashCode() { - return base.GetHashCode(); + HashCode hash = new HashCode(); + hash.Add(m_referenceTypeId); + hash.Add(m_isInverse); + hash.Add(m_targetId); + return hash.ToHashCode(); } /// @@ -710,9 +724,8 @@ public VariableNode(ILocalNode source) : base(source) { this.NodeClass = NodeClass.Variable; - IVariable variable = source as IVariable; - if (variable != null) + if (source is IVariable variable) { this.DataType = variable.DataType; this.ValueRank = variable.ValueRank; @@ -945,9 +958,8 @@ public ObjectNode(ILocalNode source) : base(source) { this.NodeClass = NodeClass.Object; - IObject node = source as IObject; - if (node != null) + if (source is IObject node) { this.EventNotifier = node.EventNotifier; } @@ -1022,9 +1034,8 @@ public ObjectTypeNode(ILocalNode source) : base(source) { this.NodeClass = NodeClass.ObjectType; - IObjectType node = source as IObjectType; - if (node != null) + if (source is IObjectType node) { this.IsAbstract = node.IsAbstract; } @@ -1099,9 +1110,8 @@ public VariableTypeNode(ILocalNode source) : base(source) { this.NodeClass = NodeClass.VariableType; - IVariableType node = source as IVariableType; - if (node != null) + if (source is IVariableType node) { this.IsAbstract = node.IsAbstract; this.Value = new Variant(node.Value); @@ -1311,9 +1321,8 @@ public ReferenceTypeNode(ILocalNode source) : base(source) { this.NodeClass = NodeClass.ReferenceType; - IReferenceType node = source as IReferenceType; - if (node != null) + if (source is IReferenceType node) { this.IsAbstract = node.IsAbstract; this.InverseName = node.InverseName; @@ -1396,9 +1405,8 @@ public MethodNode(ILocalNode source) : base(source) { this.NodeClass = NodeClass.Method; - IMethod node = source as IMethod; - if (node != null) + if (source is IMethod node) { this.Executable = node.Executable; this.UserExecutable = node.UserExecutable; @@ -1477,9 +1485,8 @@ public ViewNode(ILocalNode source) : base(source) { this.NodeClass = NodeClass.View; - IView node = source as IView; - if (node != null) + if (source is IView node) { this.EventNotifier = node.EventNotifier; this.ContainsNoLoops = node.ContainsNoLoops; @@ -1558,9 +1565,8 @@ public DataTypeNode(ILocalNode source) : base(source) { this.NodeClass = NodeClass.DataType; - IDataType node = source as IDataType; - if (node != null) + if (source is IDataType node) { this.IsAbstract = node.IsAbstract; } diff --git a/Stack/Opc.Ua.Core/Stack/Nodes/NodeSet.cs b/Stack/Opc.Ua.Core/Stack/Nodes/NodeSet.cs index b451a13b0..381be2a67 100644 --- a/Stack/Opc.Ua.Core/Stack/Nodes/NodeSet.cs +++ b/Stack/Opc.Ua.Core/Stack/Nodes/NodeSet.cs @@ -229,9 +229,7 @@ private object TranslateValue(object value, NamespaceTable namespaceUris, String case BuiltInType.ExtensionObject: { - Argument argument = ExtensionObject.ToEncodeable((ExtensionObject)value) as Argument; - - if (argument != null) + if (ExtensionObject.ToEncodeable((ExtensionObject)value) is Argument argument) { argument.DataType = Translate(argument.DataType, m_namespaceUris, namespaceUris); } @@ -260,9 +258,8 @@ public Node Add(ILocalNode nodeToExport, NamespaceTable namespaceUris, StringTab node.NodeId = Translate(nodeToExport.NodeId, m_namespaceUris, namespaceUris); node.BrowseName = Translate(nodeToExport.BrowseName, m_namespaceUris, namespaceUris); - VariableNode variableToExport = nodeToExport as VariableNode; - if (variableToExport != null) + if (nodeToExport is VariableNode variableToExport) { VariableNode variableNode = (VariableNode)node; @@ -272,9 +269,8 @@ public Node Add(ILocalNode nodeToExport, NamespaceTable namespaceUris, StringTab variableNode.DataType = Translate(variableToExport.DataType, m_namespaceUris, namespaceUris); } - VariableTypeNode variableTypeToExport = nodeToExport as VariableTypeNode; - if (variableTypeToExport != null) + if (nodeToExport is VariableTypeNode variableTypeToExport) { VariableTypeNode variableTypeNode = (VariableTypeNode)node; @@ -427,9 +423,8 @@ public Node Copy(Node nodeToImport, NamespaceTable namespaceUris, StringTable se node.NodeId = Translate(nodeToImport.NodeId, namespaceUris, m_namespaceUris); node.BrowseName = Translate(nodeToImport.BrowseName, namespaceUris, m_namespaceUris); - VariableNode variableToImport = nodeToImport as VariableNode; - if (variableToImport != null) + if (nodeToImport is VariableNode variableToImport) { VariableNode variable = (VariableNode)node; @@ -441,9 +436,8 @@ public Node Copy(Node nodeToImport, NamespaceTable namespaceUris, StringTable se } } - VariableTypeNode variableTypeToImport = nodeToImport as VariableTypeNode; - if (variableTypeToImport != null) + if (nodeToImport is VariableTypeNode variableTypeToImport) { VariableTypeNode variableType = (VariableTypeNode)node; @@ -478,9 +472,7 @@ public Node Copy(Node nodeToImport, NamespaceTable namespaceUris, StringTable se /// private object ImportValue(object value, NamespaceTable namespaceUris, StringTable serverUris) { - Array array = value as Array; - - if (array != null) + if (value is Array array) { Type elementType = array.GetType().GetElementType(); @@ -513,13 +505,10 @@ private object ImportValue(object value, NamespaceTable namespaceUris, StringTab return Import(expandedNodeId, namespaceUris, serverUris); } - ExtensionObject extension = value as ExtensionObject; - if (extension != null) + if (value is ExtensionObject extension) { - Argument argument = ExtensionObject.ToEncodeable(extension) as Argument; - - if (argument != null) + if (ExtensionObject.ToEncodeable(extension) is Argument argument) { argument.DataType = Import(argument.DataType, namespaceUris); } diff --git a/Stack/Opc.Ua.Core/Stack/Nodes/NodeTable.cs b/Stack/Opc.Ua.Core/Stack/Nodes/NodeTable.cs index 71b08c34f..e060bc4bd 100644 --- a/Stack/Opc.Ua.Core/Stack/Nodes/NodeTable.cs +++ b/Stack/Opc.Ua.Core/Stack/Nodes/NodeTable.cs @@ -25,19 +25,19 @@ public interface INodeTable /// /// The namespace URIs. NamespaceTable NamespaceUris { get; } - + /// /// The table of Server URIs used by the table. /// /// The server URIs. StringTable ServerUris { get; } - + /// /// The type model that describes the nodes in the table. /// /// The type tree. ITypeTable TypeTree { get; } - + /// /// Returns true if the node is in the table. /// @@ -51,7 +51,7 @@ public interface INodeTable /// The node identifier. /// Returns null if the node does not exist. INode Find(ExpandedNodeId nodeId); - + /// /// Follows the reference from the source and returns the first target with the specified browse name. /// @@ -64,11 +64,11 @@ public interface INodeTable /// Returns null if the source does not exist or if there is no matching target. /// INode Find( - ExpandedNodeId sourceId, - NodeId referenceTypeId, - bool isInverse, - bool includeSubtypes, - QualifiedName browseName); + ExpandedNodeId sourceId, + NodeId referenceTypeId, + bool isInverse, + bool includeSubtypes, + QualifiedName browseName); /// /// Follows the reference from the source and returns all target nodes. @@ -81,12 +81,12 @@ INode Find( /// Returns an empty list if the source does not exist or if there are no matching targets. /// IList Find( - ExpandedNodeId sourceId, - NodeId referenceTypeId, - bool isInverse, - bool includeSubtypes); + ExpandedNodeId sourceId, + NodeId referenceTypeId, + bool isInverse, + bool includeSubtypes); } - + /// /// A table of nodes. /// @@ -101,35 +101,35 @@ public class NodeTable : INodeTable, IEnumerable /// The server URIs. /// The type tree. public NodeTable( - NamespaceTable namespaceUris, - StringTable serverUris, - TypeTable typeTree) + NamespaceTable namespaceUris, + StringTable serverUris, + TypeTable typeTree) { m_namespaceUris = namespaceUris; - m_serverUris = serverUris; - m_typeTree = typeTree; - m_localNodes = new NodeIdDictionary(); - m_remoteNodes = new SortedDictionary(); + m_serverUris = serverUris; + m_typeTree = typeTree; + m_localNodes = new NodeIdDictionary(); + m_remoteNodes = new SortedDictionary(); } #endregion - + #region INodeTable Methods /// - public NamespaceTable NamespaceUris - { - get { return m_namespaceUris; } + public NamespaceTable NamespaceUris + { + get { return m_namespaceUris; } } /// - public StringTable ServerUris - { - get { return m_serverUris; } + public StringTable ServerUris + { + get { return m_serverUris; } } /// - public ITypeTable TypeTree - { - get { return m_typeTree; } + public ITypeTable TypeTree + { + get { return m_typeTree; } } /// @@ -146,11 +146,11 @@ public INode Find(ExpandedNodeId nodeId) /// public INode Find( - ExpandedNodeId sourceId, - NodeId referenceTypeId, - bool isInverse, - bool includeSubtypes, - QualifiedName browseName) + ExpandedNodeId sourceId, + NodeId referenceTypeId, + bool isInverse, + bool includeSubtypes, + QualifiedName browseName) { // find the source. INode source = InternalFind(sourceId); @@ -160,19 +160,18 @@ public INode Find( return null; } - ILocalNode sourceNode = source as ILocalNode; - + // can't follow references for remote nodes. - if (sourceNode == null) + if (!(source is ILocalNode sourceNode)) { return null; } // find the references. ICollection references = sourceNode.References.Find( - referenceTypeId, - isInverse, - includeSubtypes, + referenceTypeId, + isInverse, + includeSubtypes, m_typeTree); // look for the target. @@ -202,10 +201,10 @@ public INode Find( /// public IList Find( - ExpandedNodeId sourceId, - NodeId referenceTypeId, - bool isInverse, - bool includeSubtypes) + ExpandedNodeId sourceId, + NodeId referenceTypeId, + bool isInverse, + bool includeSubtypes) { // create an empty list. IList nodes = new List(); @@ -217,20 +216,19 @@ public IList Find( { return nodes; } - - ILocalNode sourceNode = source as ILocalNode; - + + // can't follow references for remote nodes. - if (sourceNode == null) + if (!(source is ILocalNode sourceNode)) { return nodes; } - + // find the references. ICollection references = sourceNode.References.Find( - referenceTypeId, - isInverse, - includeSubtypes, + referenceTypeId, + isInverse, + includeSubtypes, m_typeTree); // look for the targets. @@ -248,7 +246,7 @@ public IList Find( return nodes; } #endregion - + #region IEnumerable Members /// /// Returns an enumerator that iterates through the collection. @@ -264,7 +262,7 @@ public IEnumerator GetEnumerator() { list.Add(node); } - + foreach (INode node in m_remoteNodes.Values) { list.Add(node); @@ -296,7 +294,7 @@ public int Count { get { - return m_localNodes.Count + m_remoteNodes.Count; + return m_localNodes.Count + m_remoteNodes.Count; } } @@ -306,7 +304,7 @@ public int Count /// The node set. /// The external references. /// - public List Import(NodeSet nodeSet, IDictionary> externalReferences) + public List Import(NodeSet nodeSet, IDictionary> externalReferences) { List importedNodes = new List(); @@ -337,7 +335,7 @@ public List Import(NodeSet nodeSet, IDictionary> { node.DisplayName = new LocalizedText(node.BrowseName.Name); } - + // index references (the node ids in the references were translated by the Copy() call above). foreach (ReferenceNode reference in node.References) { @@ -361,9 +359,7 @@ public List Import(NodeSet nodeSet, IDictionary> // see if a remote node needs to be created. if (targetId.ServerIndex != 0) { - RemoteNode remoteNode = Find(targetId) as RemoteNode; - - if (remoteNode == null) + if (!(Find(targetId) is RemoteNode remoteNode)) { remoteNode = new RemoteNode(this, targetId); InternalAdd(remoteNode); @@ -375,12 +371,12 @@ public List Import(NodeSet nodeSet, IDictionary> // clear imported references. node.References.Clear(); - + // add the node. InternalAdd(node); importedNodes.Add(node); } - + // import the nodes. foreach (Node node in importedNodes) { @@ -393,9 +389,7 @@ public List Import(NodeSet nodeSet, IDictionary> // add reverse references. foreach (IReference reference in node.ReferenceTable) { - Node targetNode = Find(reference.TargetId) as Node; - - if (targetNode == null) + if (!(Find(reference.TargetId) is Node targetNode)) { if (reference.TargetId.ServerIndex != 0) { @@ -422,22 +416,22 @@ public List Import(NodeSet nodeSet, IDictionary> ReferenceNode reverseReference = new ReferenceNode(); reverseReference.ReferenceTypeId = reference.ReferenceTypeId; - reverseReference.IsInverse = !reference.IsInverse; - reverseReference.TargetId = node.NodeId; + reverseReference.IsInverse = !reference.IsInverse; + reverseReference.TargetId = node.NodeId; referenceList.Add(reverseReference); } continue; } - + // type definition and modelling rule references are one way. if (reference.ReferenceTypeId != ReferenceTypeIds.HasTypeDefinition && reference.ReferenceTypeId != ReferenceTypeIds.HasModellingRule) { targetNode.ReferenceTable.Add(reference.ReferenceTypeId, !reference.IsInverse, node.NodeId); } } - + // see if it is a type. if (m_typeTree != null) { @@ -447,7 +441,7 @@ public List Import(NodeSet nodeSet, IDictionary> return importedNodes; } - + /// /// Creates/updates a node from a ReferenceDescription. /// @@ -457,7 +451,7 @@ public INode Import(ReferenceDescription reference) { // find any existing node. INode target = Find(reference.NodeId); - + // create a new object. if (target == null) { @@ -477,14 +471,13 @@ public INode Import(ReferenceDescription reference) target = node; } } - + // update local node attributes. - Node targetNode = target as Node; - if (targetNode != null) + if (target is Node targetNode) { - targetNode.NodeClass = reference.NodeClass; - targetNode.BrowseName = reference.BrowseName; + targetNode.NodeClass = reference.NodeClass; + targetNode.BrowseName = reference.BrowseName; targetNode.DisplayName = reference.DisplayName; if (!NodeId.IsNull(reference.TypeDefinition)) @@ -493,16 +486,15 @@ public INode Import(ReferenceDescription reference) } return targetNode; - } - + } + // update remote node attributes. - RemoteNode remoteNode = target as RemoteNode; - if (remoteNode != null) + if (target is RemoteNode remoteNode) { - remoteNode.NodeClass = reference.NodeClass; - remoteNode.BrowseName = reference.BrowseName; - remoteNode.DisplayName = reference.DisplayName; + remoteNode.NodeClass = reference.NodeClass; + remoteNode.BrowseName = reference.BrowseName; + remoteNode.DisplayName = reference.DisplayName; remoteNode.TypeDefinitionId = reference.TypeDefinition; return remoteNode; @@ -528,9 +520,8 @@ public void Attach(ILocalNode node) } // check if importing a node from a XML source (must copy references from References array to ReferenceTable). - Node serializedNode = node as Node; - if (serializedNode != null && serializedNode.References.Count > 0 && serializedNode.ReferenceTable.Count == 0) + if (node is Node serializedNode && serializedNode.References.Count > 0 && serializedNode.ReferenceTable.Count == 0) { // index references. foreach (ReferenceNode reference in node.References) @@ -546,9 +537,7 @@ public void Attach(ILocalNode node) // see if a remote node needs to be created. if (reference.TargetId.ServerIndex != 0) { - RemoteNode remoteNode = Find(reference.TargetId) as RemoteNode; - - if (remoteNode == null) + if (!(Find(reference.TargetId) is RemoteNode remoteNode)) { remoteNode = new RemoteNode(this, reference.TargetId); InternalAdd(remoteNode); @@ -564,13 +553,11 @@ public void Attach(ILocalNode node) // add the node to the table. InternalAdd(node); - + // add reverse references. foreach (IReference reference in node.References) { - ILocalNode targetNode = Find(reference.TargetId) as ILocalNode; - - if (targetNode == null) + if (!(Find(reference.TargetId) is ILocalNode targetNode)) { continue; } @@ -604,14 +591,13 @@ public bool Remove(ExpandedNodeId nodeId) return false; } - ILocalNode sourceNode = source as ILocalNode; - + // can only directly remove local nodes. - if (sourceNode == null) + if (!(source is ILocalNode sourceNode)) { return false; } - + // remove references. foreach (IReference reference in sourceNode.References) { @@ -623,22 +609,20 @@ public bool Remove(ExpandedNodeId nodeId) } // remove remote node if nothing else references it. - RemoteNode remoteNode = target as RemoteNode; - if (remoteNode != null) + if (target is RemoteNode remoteNode) { if (remoteNode.Release() == 0) { InternalRemove(remoteNode); } - + continue; } // remote inverse references. - ILocalNode targetNode = target as ILocalNode; - - if (targetNode != null) + + if (target is ILocalNode targetNode) { targetNode.References.Remove(reference.ReferenceTypeId, reference.IsInverse, sourceNode.NodeId); } @@ -656,9 +640,9 @@ public void Clear() { m_localNodes.Clear(); m_remoteNodes.Clear(); - } + } #endregion - + #region Private Methods /// /// Adds the node to the table. @@ -673,7 +657,7 @@ private void InternalAdd(ILocalNode node) m_localNodes.Add(node.NodeId, node); } - + /// /// Removes the node from the table. /// @@ -687,7 +671,7 @@ private void InternalRemove(ILocalNode node) m_localNodes.Remove(node.NodeId); } - + /// /// Adds the remote node to the table. /// @@ -701,7 +685,7 @@ private void InternalAdd(RemoteNode node) m_remoteNodes[node.NodeId] = node; } - + /// /// Removes the remote node from the table. /// @@ -712,7 +696,7 @@ private void InternalRemove(RemoteNode node) { return; } - + m_remoteNodes.Remove(node.NodeId); } @@ -739,8 +723,8 @@ private INode InternalFind(ExpandedNodeId nodeId) } return null; - } - + } + // convert to locale node id. NodeId localId = ExpandedNodeId.ToNodeId(nodeId, m_namespaceUris); @@ -749,19 +733,19 @@ private INode InternalFind(ExpandedNodeId nodeId) { return null; } - + ILocalNode node = null; - + if (m_localNodes.TryGetValue(localId, out node)) { return node; } - + // node not found. return null; } #endregion - + #region RemoteNode Class /// /// Stores information for a node on a remote server. @@ -776,11 +760,11 @@ private class RemoteNode : INode /// The node identifier. public RemoteNode(INodeTable owner, ExpandedNodeId nodeId) { - m_nodeId = nodeId; - m_refs = 0; - m_nodeClass = NodeClass.Unspecified; - m_browseName = new QualifiedName("(Unknown)"); - m_displayName = new LocalizedText(m_browseName.Name); + m_nodeId = nodeId; + m_refs = 0; + m_nodeClass = NodeClass.Unspecified; + m_browseName = new QualifiedName("(Unknown)"); + m_displayName = new LocalizedText(m_browseName.Name); m_typeDefinitionId = null; } @@ -806,7 +790,7 @@ public int Release() return --m_refs; } - + /// /// The cached type definition id for the remote node. /// @@ -869,10 +853,10 @@ public LocalizedText DisplayName #endregion } #endregion - + #region Private Fields private NodeIdDictionary m_localNodes; - private SortedDictionary m_remoteNodes; + private SortedDictionary m_remoteNodes; private NamespaceTable m_namespaceUris; private StringTable m_serverUris; private TypeTable m_typeTree; diff --git a/Stack/Opc.Ua.Core/Stack/Nodes/ReferenceTable.cs b/Stack/Opc.Ua.Core/Stack/Nodes/ReferenceTable.cs index 36083d869..69d929e34 100644 --- a/Stack/Opc.Ua.Core/Stack/Nodes/ReferenceTable.cs +++ b/Stack/Opc.Ua.Core/Stack/Nodes/ReferenceTable.cs @@ -15,7 +15,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.Collections.Generic; namespace Opc.Ua -{ +{ /// /// A reference to a node. /// @@ -41,7 +41,7 @@ public interface IReference /// The target identifier. ExpandedNodeId TargetId { get; } } - + /// /// A reference to a node. /// @@ -54,8 +54,8 @@ public interface IReferenceCollection : ICollection, IEnumerableif set to true this is inverse reference. /// The target identifier. void Add( - NodeId referenceTypeId, - bool isInverse, + NodeId referenceTypeId, + bool isInverse, ExpandedNodeId targetId); /// @@ -66,8 +66,8 @@ void Add( /// The target identifier. /// The result of removal. bool Remove( - NodeId referenceTypeId, - bool isInverse, + NodeId referenceTypeId, + bool isInverse, ExpandedNodeId targetId); /// @@ -77,8 +77,8 @@ bool Remove( /// if set to true this is inverse reference. /// The result of removal. bool RemoveAll( - NodeId referenceTypeId, - bool isInverse); + NodeId referenceTypeId, + bool isInverse); /// /// Checks whether any references which meet the specified critia exist. @@ -90,11 +90,11 @@ bool RemoveAll( /// The type tree. /// True if reference exists. bool Exists( - NodeId referenceTypeId, - bool isInverse, - ExpandedNodeId targetId, - bool includeSubtypes, - ITypeTable typeTree); + NodeId referenceTypeId, + bool isInverse, + ExpandedNodeId targetId, + bool includeSubtypes, + ITypeTable typeTree); /// /// Returns a list of references which match the specified criteria. @@ -105,9 +105,9 @@ bool Exists( /// The type tree. /// A list of references which match the specified criteria. IList Find( - NodeId referenceTypeId, - bool isInverse, - bool includeSubtypes, + NodeId referenceTypeId, + bool isInverse, + bool includeSubtypes, ITypeTable typeTree); /// @@ -120,11 +120,11 @@ IList Find( /// The index. /// A single target that meets the specifed criteria. ExpandedNodeId FindTarget( - NodeId referenceTypeId, - bool isInverse, - bool includeSubtypes, - ITypeTable typeTree, - int index); + NodeId referenceTypeId, + bool isInverse, + bool includeSubtypes, + ITypeTable typeTree, + int index); /// /// Returns a list of references to the specified target. @@ -148,7 +148,7 @@ public ReferenceCollection() m_references = new IReferenceDictionary(); } #endregion - + #region IFormattable Members /// /// Returns a string representation of the ReferenceCollection. @@ -229,11 +229,11 @@ public bool RemoveAll(NodeId referenceTypeId, bool isInverse) /// The type tree. /// True if reference exists. public bool Exists( - NodeId referenceTypeId, - bool isInverse, - ExpandedNodeId targetId, - bool includeSubtypes, - ITypeTable typeTree) + NodeId referenceTypeId, + bool isInverse, + ExpandedNodeId targetId, + bool includeSubtypes, + ITypeTable typeTree) { ReferenceNode reference = new ReferenceNode(referenceTypeId, isInverse, targetId); @@ -262,9 +262,9 @@ public bool Exists( /// The type tree. /// A list of references which match the specified criteria. public IList Find( - NodeId referenceTypeId, - bool isInverse, - bool includeSubtypes, + NodeId referenceTypeId, + bool isInverse, + bool includeSubtypes, ITypeTable typeTree) { // can't search subtypes without a type tree. @@ -287,11 +287,11 @@ public IList Find( /// The index. /// A single target that meets the specifed criteria. public ExpandedNodeId FindTarget( - NodeId referenceTypeId, - bool isInverse, - bool includeSubtypes, - ITypeTable typeTree, - int index) + NodeId referenceTypeId, + bool isInverse, + bool includeSubtypes, + ITypeTable typeTree, + int index) { // get the list of matching references. IList references = null; @@ -330,24 +330,24 @@ public IList FindReferencesToTarget(ExpandedNodeId targetId) /// public int Count { - get + get { return m_references.Count; } } - + /// public bool IsReadOnly { get { return false; } } - + /// public void Add(IReference item) { m_references.Add(item, null); } - + /// public bool Remove(IReference item) { @@ -365,22 +365,22 @@ public bool Contains(IReference item) { return m_references.ContainsKey(item); } - + /// public void CopyTo(IReference[] array, int arrayIndex) { - if (array == null) + if (array == null) throw new ArgumentNullException(nameof(array)); - - if (arrayIndex < 0 || arrayIndex >= array.Length) + + if (arrayIndex < 0 || arrayIndex >= array.Length) throw new ArgumentOutOfRangeException(nameof(arrayIndex), "arrayIndex < 0 || arrayIndex >= array.Length"); - KeyValuePair[] elements = new KeyValuePair[array.Length-arrayIndex]; + KeyValuePair[] elements = new KeyValuePair[array.Length - arrayIndex]; m_references.CopyTo(elements, 0); for (int ii = 0; ii < elements.Length; ii++) { - array[arrayIndex+ii] = elements[ii].Key; + array[arrayIndex + ii] = elements[ii].Key; } } #endregion @@ -400,17 +400,17 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() return GetEnumerator(); } #endregion - + #region Private Fields private IReferenceDictionary m_references; #endregion } - + /// /// A dictionary designed to provide efficient lookups for references. /// - public class IReferenceDictionary : IDictionary - { + public class IReferenceDictionary : IDictionary + { #region Constructors /// /// Creates an empty dictionary. @@ -419,7 +419,7 @@ public IReferenceDictionary() { m_version = 0; m_references = new NodeIdDictionary(); - m_list = new LinkedList>(); + m_list = new LinkedList>(); } #endregion @@ -442,8 +442,8 @@ public bool ContainsKey( { return false; } - - foreach (KeyValuePair entry in m_references) + + foreach (KeyValuePair entry in m_references) { if (typeTree.IsTypeOf(entry.Key, reference.ReferenceTypeId)) { @@ -465,16 +465,16 @@ public bool ContainsKey( /// A list of references that match the direction and reference type. public IList Find( NodeId referenceTypeId, - bool isInverse) - { + bool isInverse) + { List hits = new List(); - + // check for null. if (NodeId.IsNull(referenceTypeId)) { return hits; } - + // look up the reference type. ReferenceTypeEntry entry = null; @@ -497,21 +497,21 @@ public IList Find( /// The type tree. /// A list of references that match the direction and are subtypes of the reference type. public IList Find( - NodeId referenceTypeId, - bool isInverse, + NodeId referenceTypeId, + bool isInverse, ITypeTable typeTree) { if (typeTree == null) throw new ArgumentNullException(nameof(typeTree)); List hits = new List(); - + // check for null. if (NodeId.IsNull(referenceTypeId)) { return hits; } - - foreach (KeyValuePair entry in m_references) + + foreach (KeyValuePair entry in m_references) { if (typeTree.IsTypeOf(entry.Key, referenceTypeId)) { @@ -530,7 +530,7 @@ public IList Find( public IList FindReferencesToTarget(ExpandedNodeId targetId) { List hits = new List(); - + // check for null. if (NodeId.IsNull(targetId)) { @@ -538,7 +538,7 @@ public IList FindReferencesToTarget(ExpandedNodeId targetId) } // go throw list of references. - for (LinkedListNode> node = m_list.First; node != null; node = node.Next) + for (LinkedListNode> node = m_list.First; node != null; node = node.Next) { if (node.Value.Key.TargetId == targetId) { @@ -570,12 +570,12 @@ public bool RemoveAll(NodeId referenceTypeId, bool isInverse) { return false; } - + if (isInverse) { if (entry.InverseTargets != null) { - foreach (LinkedListNode> node in entry.InverseTargets.Values) + foreach (LinkedListNode> node in entry.InverseTargets.Values) { if (Object.ReferenceEquals(m_list, node.List)) { @@ -585,10 +585,10 @@ public bool RemoveAll(NodeId referenceTypeId, bool isInverse) entry.InverseTargets = null; } } - + if (entry.InverseExternalTargets != null) { - foreach (LinkedListNode> node in entry.InverseExternalTargets.Values) + foreach (LinkedListNode> node in entry.InverseExternalTargets.Values) { if (Object.ReferenceEquals(m_list, node.List)) { @@ -603,7 +603,7 @@ public bool RemoveAll(NodeId referenceTypeId, bool isInverse) { if (entry.ForwardTargets != null) { - foreach (LinkedListNode> node in entry.ForwardTargets.Values) + foreach (LinkedListNode> node in entry.ForwardTargets.Values) { if (Object.ReferenceEquals(m_list, node.List)) { @@ -613,10 +613,10 @@ public bool RemoveAll(NodeId referenceTypeId, bool isInverse) entry.ForwardTargets = null; } - + if (entry.ForwardExternalTargets != null) { - foreach (LinkedListNode> node in entry.ForwardExternalTargets.Values) + foreach (LinkedListNode> node in entry.ForwardExternalTargets.Values) { if (Object.ReferenceEquals(m_list, node.List)) { @@ -644,36 +644,36 @@ public void Add(IReference key, T value) { Add(key, value, false); } - + /// public bool ContainsKey(IReference key) { - KeyValuePair target; + KeyValuePair target; if (!TryGetEntry(key, out target)) { return false; } - + return true; } - + /// public ICollection Keys { - get - { + get + { List keys = new List(); - for (LinkedListNode> node = m_list.First; node != null; node = node.Next) + for (LinkedListNode> node = m_list.First; node != null; node = node.Next) { keys.Add(node.Value.Key); } - + return keys; } } - + /// public bool Remove(IReference key) { @@ -684,7 +684,7 @@ public bool Remove(IReference key) } m_version++; - + // look up the reference type. ReferenceTypeEntry entry = null; @@ -696,7 +696,7 @@ public bool Remove(IReference key) // handle reference to external targets. if (key.TargetId.IsAbsolute) { - Dictionary>> targets = null; + Dictionary>> targets = null; if (key.IsInverse) { @@ -712,7 +712,7 @@ public bool Remove(IReference key) return false; } - LinkedListNode> node; + LinkedListNode> node; if (!targets.TryGetValue(key.TargetId, out node)) { @@ -726,7 +726,7 @@ public bool Remove(IReference key) // handle reference to internal target. else { - NodeIdDictionary>> targets = null; + NodeIdDictionary>> targets = null; if (key.IsInverse) { @@ -742,7 +742,7 @@ public bool Remove(IReference key) return false; } - LinkedListNode> node; + LinkedListNode> node; if (!targets.TryGetValue((NodeId)key.TargetId, out node)) { @@ -761,40 +761,40 @@ public bool Remove(IReference key) return true; } - + /// public bool TryGetValue(IReference key, out T value) { - value = default(T); + value = default; - KeyValuePair target; + KeyValuePair target; if (!TryGetEntry(key, out target)) { return false; } - + value = target.Value; return true; } - + /// public ICollection Values { - get - { + get + { List values = new List(); - for (LinkedListNode> node = m_list.First; node != null; node = node.Next) + for (LinkedListNode> node = m_list.First; node != null; node = node.Next) { values.Add(node.Value.Value); } - + return values; } } - + /// /// Gets or sets the value with the specified NodeId. /// @@ -805,15 +805,15 @@ public T this[IReference key] get { ValidateReference(key, true); - - KeyValuePair target; + + KeyValuePair target; if (!TryGetEntry(key, out target)) { throw new KeyNotFoundException(); } - - return target.Value; + + return target.Value; } set @@ -825,7 +825,7 @@ public T this[IReference key] #region ICollection> Members /// - public void Add(KeyValuePair item) + public void Add(KeyValuePair item) { Add(item.Key, item.Value); } @@ -837,41 +837,41 @@ public void Clear() m_references.Clear(); m_list.Clear(); } - + /// - public bool Contains(KeyValuePair item) + public bool Contains(KeyValuePair item) { - KeyValuePair target; + KeyValuePair target; if (!TryGetEntry(item.Key, out target)) { return false; } - + return Object.Equals(target.Value, item.Value); } - + /// - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { m_list.CopyTo(array, arrayIndex); } /// public int Count { - get - { + get + { return m_list.Count; } } - + /// public bool IsReadOnly { get { return false; } } - + /// public bool Remove(KeyValuePair item) { @@ -882,8 +882,8 @@ public bool Remove(KeyValuePair item) #region IEnumerable> Members /// public IEnumerator> GetEnumerator() - { - return m_list.GetEnumerator(); + { + return m_list.GetEnumerator(); } #endregion @@ -894,7 +894,7 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() return GetEnumerator(); } #endregion - + #region Private Methods #region ReferenceTypeEntry Class /// @@ -902,10 +902,10 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() /// private class ReferenceTypeEntry { - public NodeIdDictionary>> ForwardTargets; - public Dictionary>> ForwardExternalTargets; - public NodeIdDictionary>> InverseTargets; - public Dictionary>> InverseExternalTargets; + public NodeIdDictionary>> ForwardTargets; + public Dictionary>> ForwardExternalTargets; + public NodeIdDictionary>> InverseTargets; + public Dictionary>> InverseExternalTargets; /// /// Whether the entry is empty. @@ -939,8 +939,8 @@ public bool IsEmpty } } } - #endregion - + #endregion + /// /// Validates a reference passed as a parameter. /// @@ -955,7 +955,7 @@ private static bool ValidateReference(IReference key, bool throwOnError) { throw new ArgumentNullException(nameof(key), "IReference must not be null."); } - + return false; } @@ -965,7 +965,7 @@ private static bool ValidateReference(IReference key, bool throwOnError) { throw new ArgumentNullException(nameof(key), "IReference does not have a valid ReferenceTypeId."); } - + return false; } @@ -975,7 +975,7 @@ private static bool ValidateReference(IReference key, bool throwOnError) { throw new ArgumentNullException(nameof(key), "IReference does not have a valid TargetId."); } - + return false; } @@ -988,9 +988,9 @@ private static bool ValidateReference(IReference key, bool throwOnError) /// The key. /// The value. /// The target entry associated with the reference. - private bool TryGetEntry(IReference key, out KeyValuePair value) + private bool TryGetEntry(IReference key, out KeyValuePair value) { - value = new KeyValuePair(); + value = new KeyValuePair(); // validate key. if (!ValidateReference(key, false)) @@ -1009,7 +1009,7 @@ private bool TryGetEntry(IReference key, out KeyValuePair value) // handle reference to external targets. if (key.TargetId.IsAbsolute) { - Dictionary>> targets = null; + Dictionary>> targets = null; if (key.IsInverse) { @@ -1025,7 +1025,7 @@ private bool TryGetEntry(IReference key, out KeyValuePair value) return false; } - LinkedListNode> node; + LinkedListNode> node; if (targets.TryGetValue(key.TargetId, out node)) { @@ -1037,7 +1037,7 @@ private bool TryGetEntry(IReference key, out KeyValuePair value) // handle reference to internal target. else { - NodeIdDictionary>> targets = null; + NodeIdDictionary>> targets = null; if (key.IsInverse) { @@ -1052,8 +1052,8 @@ private bool TryGetEntry(IReference key, out KeyValuePair value) { return false; } - - LinkedListNode> node; + + LinkedListNode> node; if (targets.TryGetValue((NodeId)key.TargetId, out node)) { @@ -1064,7 +1064,7 @@ private bool TryGetEntry(IReference key, out KeyValuePair value) return false; } - + /// /// Adds or replaces a reference. /// @@ -1075,7 +1075,7 @@ private void Add(IReference key, T value, bool replace) { // validate key. ValidateReference(key, true); - + m_version++; // look up the reference type. @@ -1090,13 +1090,13 @@ private void Add(IReference key, T value, bool replace) // handle reference to external targets. if (key.TargetId.IsAbsolute) { - Dictionary>> targets = null; + Dictionary>> targets = null; if (key.IsInverse) { if (entry.InverseExternalTargets == null) { - entry.InverseExternalTargets = new Dictionary>>(); + entry.InverseExternalTargets = new Dictionary>>(); } targets = entry.InverseExternalTargets; @@ -1105,17 +1105,17 @@ private void Add(IReference key, T value, bool replace) { if (entry.ForwardExternalTargets == null) { - entry.ForwardExternalTargets = new Dictionary>>(); + entry.ForwardExternalTargets = new Dictionary>>(); } targets = entry.ForwardExternalTargets; } // create a new target. - LinkedListNode> node = new LinkedListNode>(new KeyValuePair(key, value)); - + LinkedListNode> node = new LinkedListNode>(new KeyValuePair(key, value)); + // check if target already exists. - LinkedListNode> existingNode = null; + LinkedListNode> existingNode = null; if (!targets.TryGetValue(key.TargetId, out existingNode)) { @@ -1134,20 +1134,20 @@ private void Add(IReference key, T value, bool replace) m_list.AddAfter(existingNode, node); m_list.Remove(existingNode); } - + targets[key.TargetId] = node; } // handle reference to internal target. else { - NodeIdDictionary>> targets = null; + NodeIdDictionary>> targets = null; if (key.IsInverse) { if (entry.InverseTargets == null) { - entry.InverseTargets = new NodeIdDictionary>>(); + entry.InverseTargets = new NodeIdDictionary>>(); } targets = entry.InverseTargets; @@ -1156,19 +1156,19 @@ private void Add(IReference key, T value, bool replace) { if (entry.ForwardTargets == null) { - entry.ForwardTargets = new NodeIdDictionary>>(); + entry.ForwardTargets = new NodeIdDictionary>>(); } targets = entry.ForwardTargets; } - + NodeId targetId = (NodeId)key.TargetId; // create a new target. - LinkedListNode> node = new LinkedListNode>(new KeyValuePair(key, value)); - + LinkedListNode> node = new LinkedListNode>(new KeyValuePair(key, value)); + // check if target already exists. - LinkedListNode> existingNode = null; + LinkedListNode> existingNode = null; if (!targets.TryGetValue(targetId, out existingNode)) { @@ -1187,7 +1187,7 @@ private void Add(IReference key, T value, bool replace) m_list.AddAfter(existingNode, node); m_list.Remove(existingNode); } - + targets[targetId] = node; } } @@ -1205,7 +1205,7 @@ private static bool ContainsKey(ReferenceTypeEntry entry, IReference reference) // handle reference to external targets. if (reference.TargetId.IsAbsolute) { - Dictionary>> targets = null; + Dictionary>> targets = null; if (reference.IsInverse) { @@ -1227,7 +1227,7 @@ private static bool ContainsKey(ReferenceTypeEntry entry, IReference reference) // handle reference to internal target. else { - NodeIdDictionary>> targets = null; + NodeIdDictionary>> targets = null; if (reference.IsInverse) { @@ -1254,23 +1254,23 @@ private static bool ContainsKey(ReferenceTypeEntry entry, IReference reference) /// if set to true this is inverse reference. /// The hits. private static void Find( - ReferenceTypeEntry entry, - bool isInverse, - List hits) + ReferenceTypeEntry entry, + bool isInverse, + List hits) { if (isInverse) { if (entry.InverseTargets != null) { - foreach (LinkedListNode> target in entry.InverseTargets.Values) + foreach (LinkedListNode> target in entry.InverseTargets.Values) { hits.Add(target.Value.Key); } } - + if (entry.InverseExternalTargets != null) { - foreach (LinkedListNode> target in entry.InverseExternalTargets.Values) + foreach (LinkedListNode> target in entry.InverseExternalTargets.Values) { hits.Add(target.Value.Key); } @@ -1280,15 +1280,15 @@ private static void Find( { if (entry.ForwardTargets != null) { - foreach (LinkedListNode> target in entry.ForwardTargets.Values) + foreach (LinkedListNode> target in entry.ForwardTargets.Values) { hits.Add(target.Value.Key); } } - + if (entry.ForwardExternalTargets != null) { - foreach (LinkedListNode> target in entry.ForwardExternalTargets.Values) + foreach (LinkedListNode> target in entry.ForwardExternalTargets.Values) { hits.Add(target.Value.Key); } @@ -1299,7 +1299,7 @@ private static void Find( #region Private Fields private NodeIdDictionary m_references; - private LinkedList> m_list; + private LinkedList> m_list; private ulong m_version; #endregion } diff --git a/Stack/Opc.Ua.Core/Stack/Nodes/TypeTable.cs b/Stack/Opc.Ua.Core/Stack/Nodes/TypeTable.cs index e5aa6b4ed..f63b062fe 100644 --- a/Stack/Opc.Ua.Core/Stack/Nodes/TypeTable.cs +++ b/Stack/Opc.Ua.Core/Stack/Nodes/TypeTable.cs @@ -11,6 +11,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace Opc.Ua { @@ -128,6 +130,18 @@ public NodeId FindSuperType(NodeId typeId) } } + /// + public Task FindSuperTypeAsync(ExpandedNodeId typeId, CancellationToken ct) + { + return Task.FromResult(FindSuperType(typeId)); + } + + /// + public Task FindSuperTypeAsync(NodeId typeId, CancellationToken ct) + { + return Task.FromResult(FindSuperType(typeId)); + } + /// public IList FindSubTypes(ExpandedNodeId typeId) { @@ -379,17 +393,15 @@ public bool IsEncodingFor(NodeId expectedTypeId, object value) } // for structure types must try to determine the subtype. - ExtensionObject extension = value as ExtensionObject; - if (extension != null) + if (value is ExtensionObject extension) { return IsEncodingFor(expectedTypeId, extension); } // every element in an array must match. - ExtensionObject[] extensions = value as ExtensionObject[]; - if (extensions != null) + if (value is ExtensionObject[] extensions) { for (int ii = 0; ii < extensions.Length; ii++) { @@ -535,7 +547,7 @@ public void Add(ILocalNode node) } } - // any new encodings. + // any new encodings. IList encodings = node.References.Find(ReferenceTypeIds.HasEncoding, false, false, null); if (encodings.Count > 0) @@ -835,7 +847,7 @@ public void GetSubtypes(List nodeIds) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private NamespaceTable m_namespaceUris; private SortedDictionary m_referenceTypes; private NodeIdDictionary m_nodes; diff --git a/Stack/Opc.Ua.Core/Stack/Nodes/ViewTable.cs b/Stack/Opc.Ua.Core/Stack/Nodes/ViewTable.cs index 79999b58b..b9dfa8924 100644 --- a/Stack/Opc.Ua.Core/Stack/Nodes/ViewTable.cs +++ b/Stack/Opc.Ua.Core/Stack/Nodes/ViewTable.cs @@ -51,7 +51,7 @@ public bool IsValid(ViewDescription description) return m_views.ContainsKey(description.ViewId); } } - + /// /// Determines whether a node is in a view. /// @@ -147,7 +147,7 @@ public void Add(ViewNode view) public void Remove(NodeId viewId) { if (NodeId.IsNull(viewId)) throw new ArgumentNullException(nameof(viewId)); - + lock (m_lock) { // find view. @@ -167,7 +167,7 @@ public void Remove(NodeId viewId) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private Dictionary m_views; #endregion } diff --git a/Stack/Opc.Ua.Core/Stack/Server/EndpointBase.cs b/Stack/Opc.Ua.Core/Stack/Server/EndpointBase.cs index 6107657a7..e06272d27 100644 --- a/Stack/Opc.Ua.Core/Stack/Server/EndpointBase.cs +++ b/Stack/Opc.Ua.Core/Stack/Server/EndpointBase.cs @@ -240,12 +240,12 @@ public virtual InvokeServiceResponseMessage InvokeService(InvokeServiceMessage r /// /// Dispatches an incoming binary encoded request. /// - public virtual IAsyncResult BeginInvokeService(InvokeServiceMessage message, AsyncCallback callack, object callbackData) + public virtual IAsyncResult BeginInvokeService(InvokeServiceMessage request, AsyncCallback callback, object asyncState) { try { // check for bad data. - if (message == null) + if (request == null) { throw new ServiceResultException(StatusCodes.BadInvalidArgument); } @@ -254,8 +254,8 @@ public virtual IAsyncResult BeginInvokeService(InvokeServiceMessage message, Asy SetRequestContext(RequestEncoding.Binary); // create handler. - ProcessRequestAsyncResult result = new ProcessRequestAsyncResult(this, callack, callbackData, 0); - return result.BeginProcessRequest(SecureChannelContext.Current, message.InvokeServiceRequest); + ProcessRequestAsyncResult result = new ProcessRequestAsyncResult(this, callback, asyncState, 0); + return result.BeginProcessRequest(SecureChannelContext.Current, request.InvokeServiceRequest); } catch (Exception e) { @@ -266,13 +266,13 @@ public virtual IAsyncResult BeginInvokeService(InvokeServiceMessage message, Asy /// /// Dispatches an incoming binary encoded request. /// - /// The async result. - public virtual InvokeServiceResponseMessage EndInvokeService(IAsyncResult ar) + /// The async result. + public virtual InvokeServiceResponseMessage EndInvokeService(IAsyncResult result) { try { // wait for the response. - IServiceResponse response = ProcessRequestAsyncResult.WaitForComplete(ar, false); + IServiceResponse response = ProcessRequestAsyncResult.WaitForComplete(result, false); // encode the response. InvokeServiceResponseMessage outgoing = new InvokeServiceResponseMessage(); @@ -282,7 +282,7 @@ public virtual InvokeServiceResponseMessage EndInvokeService(IAsyncResult ar) catch (Exception e) { // create fault. - ServiceFault fault = CreateFault(ProcessRequestAsyncResult.GetRequest(ar), e); + ServiceFault fault = CreateFault(ProcessRequestAsyncResult.GetRequest(result), e); // encode the fault as a response. InvokeServiceResponseMessage outgoing = new InvokeServiceResponseMessage(); @@ -414,9 +414,8 @@ public static ServiceFault CreateFault(IServiceRequest request, Exception except ServiceResult result = null; - ServiceResultException sre = exception as ServiceResultException; - if (sre != null) + if (exception is ServiceResultException sre) { result = new ServiceResult(sre); Utils.LogWarning("SERVER - Service Fault Occurred. Reason={0}", result.StatusCode); @@ -785,9 +784,7 @@ public IAsyncResult BeginProcessRequest( /// The response. public static IServiceResponse WaitForComplete(IAsyncResult ar, bool throwOnError) { - ProcessRequestAsyncResult result = ar as ProcessRequestAsyncResult; - - if (result == null) + if (!(ar is ProcessRequestAsyncResult result)) { throw new ArgumentException("End called with an invalid IAsyncResult object.", nameof(ar)); } @@ -815,9 +812,7 @@ public static IServiceResponse WaitForComplete(IAsyncResult ar, bool throwOnErro /// The request object if available; otherwise null. public static IServiceRequest GetRequest(IAsyncResult ar) { - ProcessRequestAsyncResult result = ar as ProcessRequestAsyncResult; - - if (result != null) + if (ar is ProcessRequestAsyncResult result) { return result.m_request; } diff --git a/Stack/Opc.Ua.Core/Stack/Server/IEndpointBase.cs b/Stack/Opc.Ua.Core/Stack/Server/IEndpointBase.cs index 2f512c259..8228f4371 100644 --- a/Stack/Opc.Ua.Core/Stack/Server/IEndpointBase.cs +++ b/Stack/Opc.Ua.Core/Stack/Server/IEndpointBase.cs @@ -14,8 +14,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. namespace Opc.Ua { - - #if OPCUA_USE_SYNCHRONOUS_ENDPOINTS + +#if OPCUA_USE_SYNCHRONOUS_ENDPOINTS /// /// The base interface for all services exposed by UA servers. /// @@ -30,7 +30,7 @@ public interface IEndpointBase [OperationContract(Action = Namespaces.OpcUaWsdl + "/InvokeService", ReplyAction = Namespaces.OpcUaWsdl + "/InvokeServiceResponse")] InvokeServiceResponseMessage InvokeService(InvokeServiceMessage request); } - #else +#else /// /// The base asynchronous interface for all services exposed by UA servers. /// @@ -46,5 +46,5 @@ public interface IEndpointBase /// InvokeServiceResponseMessage EndInvokeService(IAsyncResult result); } - #endif +#endif } diff --git a/Stack/Opc.Ua.Core/Stack/Server/IServerBase.cs b/Stack/Opc.Ua.Core/Stack/Server/IServerBase.cs index 48b8ac63d..4226d7c7c 100644 --- a/Stack/Opc.Ua.Core/Stack/Server/IServerBase.cs +++ b/Stack/Opc.Ua.Core/Stack/Server/IServerBase.cs @@ -46,7 +46,7 @@ public interface IServerBase : IAuditEventCallback /// Schedules an incoming request. /// /// The request. - void ScheduleIncomingRequest(IEndpointIncomingRequest request); + void ScheduleIncomingRequest(IEndpointIncomingRequest request); } /// diff --git a/Stack/Opc.Ua.Core/Stack/Server/SecureChannelContext.cs b/Stack/Opc.Ua.Core/Stack/Server/SecureChannelContext.cs index 5c6428055..c77cd0605 100644 --- a/Stack/Opc.Ua.Core/Stack/Server/SecureChannelContext.cs +++ b/Stack/Opc.Ua.Core/Stack/Server/SecureChannelContext.cs @@ -27,31 +27,31 @@ public class SecureChannelContext /// The endpoint description. /// The message encoding. public SecureChannelContext( - string secureChannelId, + string secureChannelId, EndpointDescription endpointDescription, - RequestEncoding messageEncoding) - { - m_secureChannelId = secureChannelId; + RequestEncoding messageEncoding) + { + m_secureChannelId = secureChannelId; m_endpointDescription = endpointDescription; - m_messageEncoding = messageEncoding; + m_messageEncoding = messageEncoding; } /// /// Initializes a new instance with the context for the current thread. /// protected SecureChannelContext() - { + { SecureChannelContext context = SecureChannelContext.Current; if (context != null) { - m_secureChannelId = context.SecureChannelId; + m_secureChannelId = context.SecureChannelId; m_endpointDescription = context.EndpointDescription; - m_messageEncoding = context.MessageEncoding; + m_messageEncoding = context.MessageEncoding; } } #endregion - + #region Public Properties /// /// TThe unique identifier for the secure channel. @@ -78,15 +78,15 @@ public EndpointDescription EndpointDescription public RequestEncoding MessageEncoding { get { return m_messageEncoding; } - } - #endregion + } + #endregion - #region Static Members + #region Static Members /// /// The active secure channel for the thread. /// /// The current secure channel context. - public static SecureChannelContext Current + public static SecureChannelContext Current { get { diff --git a/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs b/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs index e8d0dfc00..885f9ceb8 100644 --- a/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs +++ b/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs @@ -1671,7 +1671,7 @@ private void OnProcessRequestQueue(object state) private int m_minThreadCount; private int m_maxRequestCount; #if THREAD_SCHEDULER - private object m_lock = new object(); + private readonly object m_lock = new object(); private Queue m_queue; private int m_totalThreadCount; #endif diff --git a/Stack/Opc.Ua.Core/Stack/State/AcknowledgeableConditionState.cs b/Stack/Opc.Ua.Core/Stack/State/AcknowledgeableConditionState.cs index 2c4e55c69..fb002735a 100644 --- a/Stack/Opc.Ua.Core/Stack/State/AcknowledgeableConditionState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/AcknowledgeableConditionState.cs @@ -150,7 +150,7 @@ protected virtual ServiceResult OnAcknowledgeCalled( { AcknowledgeableConditionState branch = GetAcknowledgeableBranch(eventId); - if ( branch != null ) + if (branch != null) { branch.OnAcknowledgeCalled(context, method, objectId, eventId, comment); @@ -159,7 +159,7 @@ protected virtual ServiceResult OnAcknowledgeCalled( ReplaceBranchEvent(eventId, branch); } else - { + { RemoveBranchEvent(eventId); } } @@ -242,7 +242,7 @@ protected virtual ServiceResult ProcessBeforeAcknowledge( { return StatusCodes.BadConditionDisabled; } - + if (OnAcknowledge != null) { try @@ -510,7 +510,7 @@ public bool SupportsConfirm() { bool supportsConfirm = false; - if (this.ConfirmedState != null && this.ConfirmedState.Value != null ) + if (this.ConfirmedState != null && this.ConfirmedState.Value != null) { supportsConfirm = true; } @@ -563,7 +563,7 @@ protected override bool GetRetainState() } else { - if ( SupportsConfirm() && !this.ConfirmedState.Id.Value) + if (SupportsConfirm() && !this.ConfirmedState.Id.Value) { retainState = true; } diff --git a/Stack/Opc.Ua.Core/Stack/State/AuditEventState.cs b/Stack/Opc.Ua.Core/Stack/State/AuditEventState.cs index 2386e9296..24ae054c1 100644 --- a/Stack/Opc.Ua.Core/Stack/State/AuditEventState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/AuditEventState.cs @@ -32,8 +32,8 @@ public partial class AuditEventState /// Whether the operation that caused the event succeeded. /// When the operation started. public virtual void Initialize( - ISystemContext context, - NodeState source, + ISystemContext context, + NodeState source, EventSeverity severity, LocalizedText message, bool status, diff --git a/Stack/Opc.Ua.Core/Stack/State/BaseEventState.cs b/Stack/Opc.Ua.Core/Stack/State/BaseEventState.cs index a9f058195..dcd7e983e 100644 --- a/Stack/Opc.Ua.Core/Stack/State/BaseEventState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/BaseEventState.cs @@ -30,8 +30,8 @@ public partial class BaseEventState /// The severity for the event. /// The default message. public virtual void Initialize( - ISystemContext context, - NodeState source, + ISystemContext context, + NodeState source, EventSeverity severity, LocalizedText message) { diff --git a/Stack/Opc.Ua.Core/Stack/State/BaseInstanceState.cs b/Stack/Opc.Ua.Core/Stack/State/BaseInstanceState.cs index 099398500..ae4f57f6d 100644 --- a/Stack/Opc.Ua.Core/Stack/State/BaseInstanceState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/BaseInstanceState.cs @@ -37,9 +37,7 @@ protected BaseInstanceState(NodeClass nodeClass, NodeState parent) : base(nodeCl /// protected override void Initialize(ISystemContext context, NodeState source) { - BaseInstanceState instance = source as BaseInstanceState; - - if (instance != null) + if (source is BaseInstanceState instance) { m_referenceTypeId = instance.m_referenceTypeId; m_typeDefinitionId = instance.m_typeDefinitionId; @@ -137,9 +135,7 @@ public string GetDisplayPath(int maxLength, char seperator) while (parent != null) { - BaseInstanceState instance = parent as BaseInstanceState; - - if (instance == null) + if (!(parent is BaseInstanceState instance)) { break; } @@ -376,9 +372,7 @@ public void Update( // save the variable value. if (field.AttributeId == Attributes.Value) { - BaseVariableState variable = child as BaseVariableState; - - if (variable != null && field.AttributeId == Attributes.Value) + if (child is BaseVariableState variable && field.AttributeId == Attributes.Value) { try { @@ -406,9 +400,7 @@ public void Update( /// The minimum sampling interval. public void SetMinimumSamplingInterval(ISystemContext context, double minimumSamplingInterval) { - BaseVariableState variable = this as BaseVariableState; - - if (variable != null) + if (this is BaseVariableState variable) { variable.MinimumSamplingInterval = minimumSamplingInterval; } @@ -630,27 +622,27 @@ public override void Save(ISystemContext context, BinaryEncoder encoder, Attribu /// /// The context. /// The decoder. - /// The attributes to load. - public override void Update(ISystemContext context, BinaryDecoder decoder, AttributesToSave attibutesToLoad) + /// The attributes to load. + public override void Update(ISystemContext context, BinaryDecoder decoder, AttributesToSave attributesToLoad) { - base.Update(context, decoder, attibutesToLoad); + base.Update(context, decoder, attributesToLoad); - if ((attibutesToLoad & AttributesToSave.ReferenceTypeId) != 0) + if ((attributesToLoad & AttributesToSave.ReferenceTypeId) != 0) { m_referenceTypeId = decoder.ReadNodeId(null); } - if ((attibutesToLoad & AttributesToSave.TypeDefinitionId) != 0) + if ((attributesToLoad & AttributesToSave.TypeDefinitionId) != 0) { m_typeDefinitionId = decoder.ReadNodeId(null); } - if ((attibutesToLoad & AttributesToSave.ModellingRuleId) != 0) + if ((attributesToLoad & AttributesToSave.ModellingRuleId) != 0) { m_modellingRuleId = decoder.ReadNodeId(null); } - if ((attibutesToLoad & AttributesToSave.NumericId) != 0) + if ((attributesToLoad & AttributesToSave.NumericId) != 0) { m_numericId = decoder.ReadUInt32(null); } diff --git a/Stack/Opc.Ua.Core/Stack/State/BaseInstanceStateSnapshot.cs b/Stack/Opc.Ua.Core/Stack/State/BaseInstanceStateSnapshot.cs index a4a42a389..b59e78ea9 100644 --- a/Stack/Opc.Ua.Core/Stack/State/BaseInstanceStateSnapshot.cs +++ b/Stack/Opc.Ua.Core/Stack/State/BaseInstanceStateSnapshot.cs @@ -37,7 +37,7 @@ public object Handle /// The context. /// The state. public void Initialize( - ISystemContext context, + ISystemContext context, BaseInstanceState state) { m_typeDefinitionId = state.TypeDefinitionId; @@ -94,10 +94,10 @@ public bool IsTypeOf(FilterContext context, NodeId typeDefinitionId) /// The attribute value. Returns null if the attribute does not exist. /// public object GetAttributeValue( - FilterContext context, - NodeId typeDefinitionId, - IList relativePath, - uint attributeId, + FilterContext context, + NodeId typeDefinitionId, + IList relativePath, + uint attributeId, NumericRange indexRange) { if (!NodeId.IsNull(typeDefinitionId)) @@ -138,8 +138,8 @@ private class ChildNode public QualifiedName BrowseName; public object Value; public List Children; - } - #endregion + } + #endregion #region Private Methods /// @@ -197,12 +197,11 @@ private ChildNode CreateChildNode(ISystemContext context, BaseInstanceState stat { ChildNode node = new ChildNode(); - node.NodeClass = state.NodeClass; + node.NodeClass = state.NodeClass; node.BrowseName = state.BrowseName; - BaseVariableState variable = state as BaseVariableState; - if (variable != null) + if (state is BaseVariableState variable) { if (!StatusCode.IsBad(variable.StatusCode)) { @@ -210,11 +209,10 @@ private ChildNode CreateChildNode(ISystemContext context, BaseInstanceState stat } } - BaseObjectState instance = state as BaseObjectState; - if (instance != null) + if (state is BaseObjectState instance) { - node.Value = instance.NodeId; + node.Value = instance.NodeId; } node.Children = CreateChildNodes(context, state); @@ -245,7 +243,7 @@ private List CreateChildNodes(ISystemContext context, BaseInstanceSta } ChildNode node = CreateChildNode(context, child); - nodes.Add(node); + nodes.Add(node); } return nodes; @@ -262,7 +260,7 @@ private List CreateChildNodes(ISystemContext context, BaseInstanceSta private object GetAttributeValue( ChildNode node, IList relativePath, - int index, + int index, uint attributeId) { if (index >= relativePath.Count) @@ -294,14 +292,14 @@ private object GetAttributeValue( { if (node.Children[ii].BrowseName == relativePath[index]) { - return GetAttributeValue(node.Children[ii], relativePath, index+1, attributeId); + return GetAttributeValue(node.Children[ii], relativePath, index + 1, attributeId); } } return null; } #endregion - + #region Private Fields private NodeId m_typeDefinitionId; private ChildNode m_snapshot; diff --git a/Stack/Opc.Ua.Core/Stack/State/BaseObjectState.cs b/Stack/Opc.Ua.Core/Stack/State/BaseObjectState.cs index 051133f03..f0778de80 100644 --- a/Stack/Opc.Ua.Core/Stack/State/BaseObjectState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/BaseObjectState.cs @@ -75,9 +75,7 @@ protected override void Initialize(ISystemContext context) /// protected override void Initialize(ISystemContext context, NodeState source) { - BaseObjectState instance = source as BaseObjectState; - - if (instance != null) + if (source is BaseObjectState instance) { m_eventNotifier = instance.m_eventNotifier; } @@ -159,9 +157,8 @@ protected override void Export(ISystemContext context, Node node) { base.Export(context, node); - ObjectNode objectNode = node as ObjectNode; - if (objectNode != null) + if (node is ObjectNode objectNode) { objectNode.EventNotifier = this.EventNotifier; } diff --git a/Stack/Opc.Ua.Core/Stack/State/BaseTypeState.cs b/Stack/Opc.Ua.Core/Stack/State/BaseTypeState.cs index 28ae3d56e..dfd40b936 100644 --- a/Stack/Opc.Ua.Core/Stack/State/BaseTypeState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/BaseTypeState.cs @@ -43,9 +43,7 @@ protected BaseTypeState(NodeClass nodeClass) : base(nodeClass) /// protected override void Initialize(ISystemContext context, NodeState source) { - BaseTypeState type = source as BaseTypeState; - - if (type != null) + if (source is BaseTypeState type) { m_superTypeId = type.m_superTypeId; m_isAbstract = type.m_isAbstract; @@ -270,17 +268,17 @@ public override void Save(ISystemContext context, BinaryEncoder encoder, Attribu /// /// The context. /// The decoder. - /// The attributes to load. - public override void Update(ISystemContext context, BinaryDecoder decoder, AttributesToSave attibutesToLoad) + /// The attributes to load. + public override void Update(ISystemContext context, BinaryDecoder decoder, AttributesToSave attributesToLoad) { - base.Update(context, decoder, attibutesToLoad); + base.Update(context, decoder, attributesToLoad); - if ((attibutesToLoad & AttributesToSave.SuperTypeId) != 0) + if ((attributesToLoad & AttributesToSave.SuperTypeId) != 0) { m_superTypeId = decoder.ReadNodeId(null); } - if ((attibutesToLoad & AttributesToSave.IsAbstract) != 0) + if ((attributesToLoad & AttributesToSave.IsAbstract) != 0) { m_isAbstract = decoder.ReadBoolean(null); } diff --git a/Stack/Opc.Ua.Core/Stack/State/BaseVariableState.cs b/Stack/Opc.Ua.Core/Stack/State/BaseVariableState.cs index 1f7227f91..2dd6130ad 100644 --- a/Stack/Opc.Ua.Core/Stack/State/BaseVariableState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/BaseVariableState.cs @@ -48,9 +48,7 @@ public BaseVariableState(NodeState parent) : base(NodeClass.Variable, parent) /// A source node to be copied to this instance. protected override void Initialize(ISystemContext context, NodeState source) { - BaseVariableState instance = source as BaseVariableState; - - if (instance != null) + if (source is BaseVariableState instance) { // The value will be set to default(T) if the originating value is null m_value = ExtractValueFromVariant(context, instance.m_value, false); @@ -141,7 +139,7 @@ public static T GetValue(BaseDataVariableState variable) { if (variable == null) { - return default(T); + return default; } return variable.Value; @@ -159,7 +157,7 @@ public static T GetValue(PropertyState property) { if (property == null) { - return default(T); + return default; } return property.Value; @@ -200,9 +198,8 @@ public static object ExtractValueFromVariant(ISystemContext context, object v return value; } - ExtensionObject extension = value as ExtensionObject; - if (extension != null) + if (value is ExtensionObject extension) { if (typeof(T).IsInstanceOfType(extension.Body)) { @@ -227,9 +224,8 @@ public static object ExtractValueFromVariant(ISystemContext context, object v if (elementType != null) { // check for array of extensions. - IList extensions = value as IList; - if (extensions != null && typeof(IEncodeable).GetTypeInfo().IsAssignableFrom(elementType.GetTypeInfo())) + if (value is IList extensions && typeof(IEncodeable).GetTypeInfo().IsAssignableFrom(elementType.GetTypeInfo())) { Array encodeables = Array.CreateInstance(elementType, extensions.Count); @@ -265,9 +261,8 @@ public static object ExtractValueFromVariant(ISystemContext context, object v } // check for array of variants. - IList variants = value as IList; - if (variants != null) + if (value is IList variants) { // only support conversions to object[]. if (elementType != typeof(object)) @@ -292,9 +287,7 @@ public static object ExtractValueFromVariant(ISystemContext context, object v // check for array of uuids. if (typeof(Guid).GetTypeInfo().IsAssignableFrom(elementType.GetTypeInfo())) { - IList uuids = value as IList; - - if (uuids != null) + if (value is IList uuids) { Guid[] guids = new Guid[uuids.Count]; @@ -310,9 +303,7 @@ public static object ExtractValueFromVariant(ISystemContext context, object v // check for array of enumeration. if (typeof(Enum).GetTypeInfo().IsAssignableFrom(elementType.GetTypeInfo())) { - IList values = value as IList; - - if (values != null) + if (value is IList values) { Array enums = Array.CreateInstance(elementType, values.Count); @@ -369,9 +360,8 @@ public static object DecodeExtensionObject(ISystemContext context, Type targetTy return extension.Body; } - IEncodeable instance = Activator.CreateInstance(targetType) as IEncodeable; - if (instance != null) + if (Activator.CreateInstance(targetType) is IEncodeable instance) { IDecoder decoder = null; @@ -436,7 +426,7 @@ public static T CheckTypeBeforeCast(object value, bool throwOnError) throw ServiceResultException.Create(StatusCodes.BadTypeMismatch, "Cannot convert '{0}' to a {1}.", value, typeof(T).Name); } - return default(T); + return default; } return (T)value; @@ -885,9 +875,8 @@ protected override void Export(ISystemContext context, Node node) { base.Export(context, node); - VariableNode variableNode = node as VariableNode; - if (variableNode != null) + if (node is VariableNode variableNode) { try { @@ -2117,7 +2106,7 @@ public class PropertyState : PropertyState /// public PropertyState(NodeState parent) : base(parent) { - Value = default(T); + Value = default; IsValueType = !typeof(T).GetTypeInfo().IsValueType; } #endregion @@ -2130,7 +2119,7 @@ protected override void Initialize(ISystemContext context) { base.Initialize(context); - Value = default(T); + Value = default; DataType = TypeInfo.GetDataTypeId(typeof(T)); ValueRank = TypeInfo.GetValueRank(typeof(T)); } @@ -2335,7 +2324,7 @@ public class BaseDataVariableState : BaseDataVariableState /// public BaseDataVariableState(NodeState parent) : base(parent) { - Value = default(T); + Value = default; IsValueType = !typeof(T).GetTypeInfo().IsValueType; } #endregion @@ -2349,7 +2338,7 @@ protected override void Initialize(ISystemContext context) { base.Initialize(context); - Value = default(T); + Value = default; DataType = TypeInfo.GetDataTypeId(typeof(T)); ValueRank = TypeInfo.GetValueRank(typeof(T)); } @@ -2623,9 +2612,8 @@ protected void SetUpdateList(IList updateList) m_updateList[ii] = updateList[ii]; // the copy copy is enforced by the value wrapper. - BaseVariableState variable = m_updateList[ii] as BaseVariableState; - if (variable != null) + if (m_updateList[ii] is BaseVariableState variable) { variable.CopyPolicy = VariableCopyPolicy.Never; } diff --git a/Stack/Opc.Ua.Core/Stack/State/BaseVariableTypeState.cs b/Stack/Opc.Ua.Core/Stack/State/BaseVariableTypeState.cs index b59491bd8..7f37c5b8c 100644 --- a/Stack/Opc.Ua.Core/Stack/State/BaseVariableTypeState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/BaseVariableTypeState.cs @@ -36,9 +36,7 @@ protected BaseVariableTypeState() : base(NodeClass.VariableType) /// protected override void Initialize(ISystemContext context, NodeState source) { - BaseVariableTypeState type = source as BaseVariableTypeState; - - if (type != null) + if (source is BaseVariableTypeState type) { m_value = Utils.Clone(type.m_value); m_dataType = type.m_dataType; @@ -239,9 +237,8 @@ protected override void Export(ISystemContext context, Node node) { base.Export(context, node); - VariableTypeNode variableTypeNode = node as VariableTypeNode; - if (variableTypeNode != null) + if (node is VariableTypeNode variableTypeNode) { variableTypeNode.Value = new Variant(Utils.Clone(this.Value)); variableTypeNode.DataType = this.DataType; @@ -794,7 +791,7 @@ protected override void Initialize(ISystemContext context) { base.Initialize(context); - Value = default(T); + Value = default; DataType = TypeInfo.GetDataTypeId(typeof(T)); ValueRank = TypeInfo.GetValueRank(typeof(T)); } @@ -895,7 +892,7 @@ protected override void Initialize(ISystemContext context) { base.Initialize(context); - Value = default(T); + Value = default; DataType = TypeInfo.GetDataTypeId(typeof(T)); ValueRank = TypeInfo.GetValueRank(typeof(T)); } diff --git a/Stack/Opc.Ua.Core/Stack/State/ConditionState.cs b/Stack/Opc.Ua.Core/Stack/State/ConditionState.cs index 7e97ab713..3dd41ea08 100644 --- a/Stack/Opc.Ua.Core/Stack/State/ConditionState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/ConditionState.cs @@ -48,7 +48,7 @@ protected override void OnAfterCreate(ISystemContext context, NodeState node) } } #endregion - + #region Public Methods /// /// Gets or sets a value indicating whether the condition will automatically report an event when a method call completes. @@ -152,19 +152,19 @@ public virtual void SetComment( /// The system context. /// The Desired Branch Id /// ConditionState newly created branch - public virtual ConditionState CreateBranch( ISystemContext context, NodeId branchId ) + public virtual ConditionState CreateBranch(ISystemContext context, NodeId branchId) { ConditionState state = null; Type alarmType = this.GetType(); object branchedAlarm = Activator.CreateInstance(alarmType, this); - if ( branchedAlarm != null ) + if (branchedAlarm != null) { ConditionState branchedNodeState = (ConditionState)branchedAlarm; branchedNodeState.Initialize(context, this); branchedNodeState.BranchId.Value = branchId; branchedNodeState.AutoReportStateChanges = AutoReportStateChanges; - branchedNodeState.ReportStateChange(context, false ); + branchedNodeState.ReportStateChange(context, false); string postEventId = Utils.ToHexString(branchedNodeState.EventId.Value as byte[]); @@ -186,7 +186,7 @@ public virtual ConditionState CreateBranch( ISystemContext context, NodeId branc /// Function exists because constructor is in auto generated code. /// /// - public Dictionary< string, ConditionState >GetBranches() + public Dictionary GetBranches() { if (m_branches == null) { @@ -288,7 +288,7 @@ protected virtual void UpdateRetainState() { bool retainState = GetRetainState(); - if ( this.Retain.Value != retainState ) + if (this.Retain.Value != retainState) { this.Retain.Value = retainState; } @@ -339,11 +339,11 @@ public virtual int GetBranchCount() /// /// /// Boolean determining if this event is monitored, and should be reported - public bool EventsMonitored( ) + public bool EventsMonitored() { bool areEventsMonitored = this.AreEventsMonitored; - if ( IsBranch() ) + if (IsBranch()) { areEventsMonitored = Parent.AreEventsMonitored; } @@ -402,7 +402,7 @@ protected void ReportStateChange(ISystemContext context, bool ignoreDisabledStat { return; } - + if (AutoReportStateChanges) { // create a new event instance. @@ -428,7 +428,7 @@ protected void ReportStateChange(ISystemContext context, bool ignoreDisabledStat /// The context. protected virtual void UpdateEffectiveState(ISystemContext context) { - SetEffectiveSubState(context, this.EnabledState.Value, DateTime.MinValue); + SetEffectiveSubState(context, this.EnabledState.Value, DateTime.MinValue); } /// @@ -453,7 +453,7 @@ protected virtual ServiceResult OnAddCommentCalled( { string currentUserId = GetCurrentUserId(context); ConditionState branch = GetBranch(eventId); - if ( branch != null ) + if (branch != null) { branch.OnAddCommentCalled(context, method, objectId, eventId, comment); } @@ -507,9 +507,7 @@ protected virtual ServiceResult OnAddCommentCalled( /// The display name for the current user. protected string GetCurrentUserId(ISystemContext context) { - IOperationContext operationContext = context as IOperationContext; - - if (operationContext != null && operationContext.UserIdentity != null) + if (context is IOperationContext operationContext && operationContext.UserIdentity != null) { return operationContext.UserIdentity.DisplayName; } @@ -524,7 +522,7 @@ protected string GetCurrentUserId(ISystemContext context) /// The identifier for the event which is the target for the comment. /// The comment. protected virtual ServiceResult ProcessBeforeAddComment( - ISystemContext context, + ISystemContext context, byte[] eventId, LocalizedText comment) { @@ -569,7 +567,7 @@ protected virtual ServiceResult OnEnableCalled( Dictionary branches = GetBranches(); // Enable all branches - foreach ( ConditionState branch in branches.Values ) + foreach (ConditionState branch in branches.Values) { branch.OnEnableCalled(context, method, inputArguments, outputArguments); } @@ -600,7 +598,7 @@ protected virtual ServiceResult OnEnableCalled( new LocalizedText(info), ServiceResult.IsGood(error), DateTime.UtcNow); - + e.SetChildValue(context, BrowseNames.SourceNode, NodeId, false); e.SetChildValue(context, BrowseNames.SourceName, "Method/Enable", false); e.SetChildValue(context, BrowseNames.MethodId, method.NodeId, false); @@ -669,7 +667,7 @@ protected virtual ServiceResult OnDisableCalled( return error; } - + /// /// Does any processing before a condition is enabled or disabled. /// @@ -775,7 +773,7 @@ protected bool IsBranch() /// The condition that raised the event. /// True if the condition is moving/has moved to the Enabled state. public delegate ServiceResult ConditionEnableEventHandler( - ISystemContext context, + ISystemContext context, ConditionState condition, bool enabling); diff --git a/Stack/Opc.Ua.Core/Stack/State/DialogConditionState.cs b/Stack/Opc.Ua.Core/Stack/State/DialogConditionState.cs index 74fccd93c..8699c4237 100644 --- a/Stack/Opc.Ua.Core/Stack/State/DialogConditionState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/DialogConditionState.cs @@ -102,7 +102,7 @@ public virtual void SetResponse(ISystemContext context, int response) /// /// The context. protected override void UpdateEffectiveState(ISystemContext context) - { + { if (!this.EnabledState.Id.Value) { base.UpdateEffectiveState(context); @@ -196,7 +196,7 @@ protected virtual ServiceResult OnRespondCalled( e.SetChildValue(context, BrowseNames.InputArguments, new object[] { selectedResponse }, false); e.SetChildValue(context, BrowseNames.SelectedResponse, selectedResponse.ToString(), false); - + ReportEvent(context, e); } } diff --git a/Stack/Opc.Ua.Core/Stack/State/ExclusiveLimitAlarmState.cs b/Stack/Opc.Ua.Core/Stack/State/ExclusiveLimitAlarmState.cs index 7352be2a9..10a574ff9 100644 --- a/Stack/Opc.Ua.Core/Stack/State/ExclusiveLimitAlarmState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/ExclusiveLimitAlarmState.cs @@ -98,10 +98,10 @@ public virtual void SetLimitState( this.LimitState.SetState(context, 0); break; } - } + } - SetActiveEffectiveSubState(context, this.LimitState.CurrentState.Value, DateTime.UtcNow); - base.SetActiveState(context, limit != LimitAlarmStates.Inactive); + SetActiveEffectiveSubState(context, this.LimitState.CurrentState.Value, DateTime.UtcNow); + base.SetActiveState(context, limit != LimitAlarmStates.Inactive); } #endregion } diff --git a/Stack/Opc.Ua.Core/Stack/State/FiniteStateMachineState.cs b/Stack/Opc.Ua.Core/Stack/State/FiniteStateMachineState.cs index 57b4694b2..28d0e37fd 100644 --- a/Stack/Opc.Ua.Core/Stack/State/FiniteStateMachineState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/FiniteStateMachineState.cs @@ -530,7 +530,7 @@ public virtual ServiceResult DoCause( // do the transition. result = DoTransition(context, transitionId, causeId, inputArguments, outputArguments); - + if (ServiceResult.IsBad(result)) { return result; @@ -548,7 +548,7 @@ public virtual ServiceResult DoCause( UpdateAuditEvent(context, causeMethod, inputArguments, causeId, e, result); ReportEvent(context, e); - if(m_causeId != causeId) + if (m_causeId != causeId) { ReportAuditProgramTransitionEvent(context, causeMethod, causeId, inputArguments, result); m_causeId = causeId; @@ -584,9 +584,9 @@ protected virtual void UpdateAuditEvent( TranslationInfo info = new TranslationInfo( "StateTransition", "en-US", - "The {1} method called was on the {0} state machine.", - this.GetDisplayPath(3, '.'), - causeMethod.DisplayName); + "The {0} method called was on the {1} state machine.", + causeMethod.DisplayName, + this.GetDisplayPath(3, '.')); e.Initialize( context, @@ -729,7 +729,7 @@ public ServiceResult DoTransition( // update state and transition variables. UpdateStateVariable(context, newState, CurrentState); UpdateTransitionVariable(context, transitionId, LastTransition); - + // do any post-transition processing. InvokeCallback( OnAfterTransition, @@ -744,7 +744,7 @@ public ServiceResult DoTransition( if (this.AreEventsMonitored && !m_suppressTransitionEvents) { TransitionEventState e = CreateTransitionEvent(context, transitionId, causeId); - + if (e != null) { UpdateTransitionEvent(context, transitionId, causeId, e); diff --git a/Stack/Opc.Ua.Core/Stack/State/ISystemContext.cs b/Stack/Opc.Ua.Core/Stack/State/ISystemContext.cs index 8dbe289cf..c3345d9e2 100644 --- a/Stack/Opc.Ua.Core/Stack/State/ISystemContext.cs +++ b/Stack/Opc.Ua.Core/Stack/State/ISystemContext.cs @@ -286,7 +286,7 @@ public INodeIdFactory NodeIdFactory set { m_nodeIdFactory = value; } } #endregion - + #region Public Members /// /// The operation context associated with the system context. diff --git a/Stack/Opc.Ua.Core/Stack/State/LimitAlarmState.cs b/Stack/Opc.Ua.Core/Stack/State/LimitAlarmState.cs index 72cb72e1a..7189b7b4f 100644 --- a/Stack/Opc.Ua.Core/Stack/State/LimitAlarmState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/LimitAlarmState.cs @@ -45,6 +45,6 @@ public enum LimitAlarmStates /// /// The alarm is in the LowLow state. /// - LowLow =0x8 + LowLow = 0x8 } } diff --git a/Stack/Opc.Ua.Core/Stack/State/MethodState.cs b/Stack/Opc.Ua.Core/Stack/State/MethodState.cs index 0fcfc244c..57affe957 100644 --- a/Stack/Opc.Ua.Core/Stack/State/MethodState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/MethodState.cs @@ -65,14 +65,12 @@ protected override void Initialize(ISystemContext context) /// protected override void Initialize(ISystemContext context, NodeState source) { - MethodState method = source as MethodState; - - if (method != null) + if (source is MethodState method) { m_executable = method.m_executable; m_userExecutable = method.m_userExecutable; } - + base.Initialize(context, source); } #endregion @@ -120,10 +118,10 @@ public NodeId MethodDeclarationId public bool Executable { get - { - return m_executable; + { + return m_executable; } - + set { if (m_executable != value) @@ -141,10 +139,10 @@ public bool Executable public bool UserExecutable { get - { - return m_userExecutable; + { + return m_userExecutable; } - + set { if (m_userExecutable != value) @@ -199,9 +197,8 @@ protected override void Export(ISystemContext context, Node node) { base.Export(context, node); - MethodNode methodNode = node as MethodNode; - if (methodNode != null) + if (node is MethodNode methodNode) { methodNode.Executable = this.Executable; methodNode.UserExecutable = this.UserExecutable; @@ -456,10 +453,10 @@ protected override ServiceResult WriteNonValueAttribute( public PropertyState InputArguments { get - { - return m_inputArguments; + { + return m_inputArguments; } - + set { if (!Object.ReferenceEquals(m_inputArguments, value)) @@ -477,10 +474,10 @@ public PropertyState InputArguments public PropertyState OutputArguments { get - { - return m_outputArguments; + { + return m_outputArguments; } - + set { if (!Object.ReferenceEquals(m_outputArguments, value)) @@ -641,7 +638,7 @@ public virtual ServiceResult Call( { return StatusCodes.BadTooManyArguments; } - + // validate individual arguements. bool error = false; diff --git a/Stack/Opc.Ua.Core/Stack/State/NodeBrowser.cs b/Stack/Opc.Ua.Core/Stack/State/NodeBrowser.cs index 3b0c8c1a3..349a9be86 100644 --- a/Stack/Opc.Ua.Core/Stack/State/NodeBrowser.cs +++ b/Stack/Opc.Ua.Core/Stack/State/NodeBrowser.cs @@ -196,7 +196,7 @@ public virtual void Add(IReference reference) m_references.Add(reference); } } - + /// /// Adds a reference to target entity. /// @@ -248,7 +248,7 @@ public ISystemContext SystemContext { get { return m_context; } } - + /// /// The view being browsed. /// @@ -299,7 +299,7 @@ public bool InternalOnly #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private ISystemContext m_context; private ViewDescription m_view; private NodeId m_referenceType; diff --git a/Stack/Opc.Ua.Core/Stack/State/NodeState.cs b/Stack/Opc.Ua.Core/Stack/State/NodeState.cs index 5403e13a6..f35cf297b 100644 --- a/Stack/Opc.Ua.Core/Stack/State/NodeState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/NodeState.cs @@ -1817,15 +1817,14 @@ private static BaseInstanceState UpdateUnknownChild( } // create the appropriate node. - BaseInstanceState child = factory.CreateInstance( + + if (!(factory.CreateInstance( context, parent, nodeClass, browseName, referenceTypeId, - typeDefinitionId) as BaseInstanceState; - - if (child == null) + typeDefinitionId) is BaseInstanceState child)) { throw ServiceResultException.Create( StatusCodes.BadDecodingError, @@ -2048,15 +2047,14 @@ private static BaseInstanceState UpdateUnknownChild( } // create the appropriate node. - BaseInstanceState child = factory.CreateInstance( + + if (!(factory.CreateInstance( context, parent, nodeClass, browseName, referenceTypeId, - typeDefinitionId) as BaseInstanceState; - - if (child == null) + typeDefinitionId) is BaseInstanceState child)) { throw ServiceResultException.Create( StatusCodes.BadDecodingError, @@ -2251,9 +2249,8 @@ private static BaseInstanceState UpdateUnknownChild( public NodeState GetHierarchyRoot() { // only instance nodes can be part of a hierarchy. - BaseInstanceState instance = this as BaseInstanceState; - if (instance == null || instance.Parent == null) + if (!(this is BaseInstanceState instance) || instance.Parent == null) { return this; } @@ -2544,9 +2541,7 @@ public virtual MethodState FindMethod(ISystemContext context, NodeId methodId) for (int ii = 0; ii < children.Count; ii++) { - MethodState method = children[ii] as MethodState; - - if (method != null) + if (children[ii] is MethodState method) { if (method.NodeId == methodId || method.MethodDeclarationId == methodId) { @@ -3223,17 +3218,15 @@ public virtual void UpdateValues( continue; } - BaseVariableState variableInstance = child as BaseVariableState; - if (variableInstance != null) + if (child is BaseVariableState variableInstance) { variableInstance.Value = values.EventFields[ii].Value; continue; } - BaseObjectState objectInstance = child as BaseObjectState; - if (objectInstance != null) + if (child is BaseObjectState objectInstance) { NodeId nodeId = values.EventFields[ii].Value as NodeId; @@ -3912,9 +3905,7 @@ protected virtual ServiceResult WriteNonValueAttribute( case Attributes.RolePermissions: { - ExtensionObject[] rolePermissionsArray = value as ExtensionObject[]; - - if (rolePermissionsArray == null) + if (!(value is ExtensionObject[] rolePermissionsArray)) { return StatusCodes.BadTypeMismatch; } @@ -3923,9 +3914,7 @@ protected virtual ServiceResult WriteNonValueAttribute( foreach (ExtensionObject arrayValue in rolePermissionsArray) { - RolePermissionType rolePermission = arrayValue.Body as RolePermissionType; - - if (rolePermission == null) + if (!(arrayValue.Body is RolePermissionType rolePermission)) { return StatusCodes.BadTypeMismatch; } @@ -4266,17 +4255,13 @@ public bool SetChildValue( return false; } - BaseInstanceState child = CreateChild(context, browseName) as BaseInstanceState; - if (child == null) + if (!(CreateChild(context, browseName) is BaseInstanceState child)) { return false; } - BaseVariableState variable = child as BaseVariableState; - BaseVariableState sourceVariable = source as BaseVariableState; - - if (variable != null && sourceVariable != null) + if (child is BaseVariableState variable && source is BaseVariableState sourceVariable) { if (copy) { @@ -4310,9 +4295,7 @@ public bool SetChildValue( object value, bool copy) { - BaseVariableState child = CreateChild(context, browseName) as BaseVariableState; - - if (child == null) + if (!(CreateChild(context, browseName) is BaseVariableState child)) { return false; } @@ -4489,11 +4472,25 @@ public bool RemoveReference( return false; } - if (m_references.Remove(new NodeStateReference(referenceTypeId, isInverse, targetId))) + NodeStateReference sourceRef = null; + + foreach (var m_refKey in m_references.Keys) { - m_changeMasks |= NodeStateChangeMasks.References; - OnReferenceRemoved?.Invoke(this, referenceTypeId, isInverse, targetId); - return true; + if (m_refKey.TargetId != null && m_refKey.TargetId.IdentifierText.Equals(targetId.IdentifierText)) + { + sourceRef = m_refKey as NodeStateReference; + break; + } + } + + if (sourceRef != null) + { + if (m_references.Remove(sourceRef)) + { + m_changeMasks |= NodeStateChangeMasks.References; + OnReferenceRemoved?.Invoke(this, referenceTypeId, isInverse, targetId); + return true; + } } return false; diff --git a/Stack/Opc.Ua.Core/Stack/State/ProgramStateMachineState.cs b/Stack/Opc.Ua.Core/Stack/State/ProgramStateMachineState.cs index 8497123c8..bf28264f9 100644 --- a/Stack/Opc.Ua.Core/Stack/State/ProgramStateMachineState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/ProgramStateMachineState.cs @@ -159,9 +159,7 @@ protected override void UpdateAuditEvent( // update program specific event fields. if (ServiceResult.IsGood(result)) { - ProgramTransitionAuditEventState e2 = e as ProgramTransitionAuditEventState; - - if (e2 != null) + if (e is ProgramTransitionAuditEventState e2) { e2.SetChildValue(context, BrowseNames.Transition, LastTransition, false); } diff --git a/Stack/Opc.Ua.Core/Stack/State/ReferenceTypeState.cs b/Stack/Opc.Ua.Core/Stack/State/ReferenceTypeState.cs index f8b21cdb0..9054c7aef 100644 --- a/Stack/Opc.Ua.Core/Stack/State/ReferenceTypeState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/ReferenceTypeState.cs @@ -58,9 +58,7 @@ protected override void Initialize(ISystemContext context) /// protected override void Initialize(ISystemContext context, NodeState source) { - ReferenceTypeState type = source as ReferenceTypeState; - - if (type != null) + if (source is ReferenceTypeState type) { m_inverseName = type.m_inverseName; m_symmetric = type.m_symmetric; @@ -144,9 +142,8 @@ protected override void Export(ISystemContext context, Node node) { base.Export(context, node); - ReferenceTypeNode referenceTypeNode = node as ReferenceTypeNode; - if (referenceTypeNode != null) + if (node is ReferenceTypeNode referenceTypeNode) { referenceTypeNode.InverseName = this.InverseName; referenceTypeNode.Symmetric = this.Symmetric; @@ -265,7 +262,7 @@ public override void Update(ISystemContext context, BinaryDecoder decoder, Attri } } #endregion - + #region Event Callbacks /// /// Raised when the InverseName attribute is read. @@ -420,7 +417,7 @@ protected override ServiceResult WriteNonValueAttribute( return base.WriteNonValueAttribute(context, attributeId, value); } #endregion - + #region Private Fields private LocalizedText m_inverseName; private bool m_symmetric; diff --git a/Stack/Opc.Ua.Core/Stack/State/ViewState.cs b/Stack/Opc.Ua.Core/Stack/State/ViewState.cs index 5e407f923..2f37f33e9 100644 --- a/Stack/Opc.Ua.Core/Stack/State/ViewState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/ViewState.cs @@ -61,9 +61,7 @@ protected override void Initialize(ISystemContext context) /// protected override void Initialize(ISystemContext context, NodeState source) { - ViewState instance = source as ViewState; - - if (instance != null) + if (source is ViewState instance) { m_eventNotifier = instance.m_eventNotifier; m_containsNoLoops = instance.m_containsNoLoops; @@ -169,9 +167,8 @@ protected override void Export(ISystemContext context, Node node) { base.Export(context, node); - ViewNode viewNode = node as ViewNode; - if (viewNode != null) + if (node is ViewNode viewNode) { viewNode.EventNotifier = this.EventNotifier; viewNode.ContainsNoLoops = this.ContainsNoLoops; @@ -274,17 +271,17 @@ public override void Save(ISystemContext context, BinaryEncoder encoder, Attribu /// /// The context. /// The decoder. - /// The attributes to load. - public override void Update(ISystemContext context, BinaryDecoder decoder, AttributesToSave attibutesToLoad) + /// The attributes to load. + public override void Update(ISystemContext context, BinaryDecoder decoder, AttributesToSave attributesToLoad) { - base.Update(context, decoder, attibutesToLoad); + base.Update(context, decoder, attributesToLoad); - if ((attibutesToLoad & AttributesToSave.EventNotifier) != 0) + if ((attributesToLoad & AttributesToSave.EventNotifier) != 0) { m_eventNotifier = decoder.ReadByte(null); } - if ((attibutesToLoad & AttributesToSave.ContainsNoLoops) != 0) + if ((attributesToLoad & AttributesToSave.ContainsNoLoops) != 0) { m_containsNoLoops = decoder.ReadBoolean(null); } diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs index 5ddb1a1a5..b3e6e110b 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs @@ -68,6 +68,15 @@ protected virtual void Dispose(bool disposing) m_event.Dispose(); m_event = null; } + + if (m_tcs != null) + { + if (!m_tcs.Task.IsCompleted) + { + m_tcs.TrySetCanceled(); + } + m_tcs = null; + } } } } @@ -192,6 +201,98 @@ public T End(int timeout, bool throwOnError = true) } } + /// + /// The awaitable response returned from the server. + /// + public async Task EndAsync(int timeout, bool throwOnError = true, CancellationToken ct = default) + { + // check if the request has already completed. + bool mustWait = false; + + lock (m_lock) + { + mustWait = !m_completed; + + if (mustWait) + { + m_tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } + } + + // wait for completion. + if (mustWait) + { + bool badRequestInterrupted = false; + try + { + Task awaitableTask = m_tcs.Task; +#if NET6_0_OR_GREATER + if (timeout != Int32.MaxValue) + { + awaitableTask = m_tcs.Task.WaitAsync(TimeSpan.FromMilliseconds(timeout), ct); + } + else if (ct != default) + { + awaitableTask = m_tcs.Task.WaitAsync(ct); + } +#else + if (timeout != Int32.MaxValue || ct != default) + { + Task completedTask = await Task.WhenAny(m_tcs.Task, Task.Delay(timeout, ct)).ConfigureAwait(false); + if (m_tcs.Task == completedTask) + { + if (!m_tcs.Task.Result) + { + badRequestInterrupted = true; + } + } + else + { + m_tcs.TrySetCanceled(ct); + badRequestInterrupted = true; + } + } + else +#endif + if (!await awaitableTask.ConfigureAwait(false)) + { + badRequestInterrupted = true; + } + } + catch (TimeoutException) + { + badRequestInterrupted = true; + } + catch (TaskCanceledException) + { + badRequestInterrupted = true; + } + finally + { + lock (m_lock) + { + m_tcs = null; + } + } + + if (badRequestInterrupted && throwOnError) + { + throw new ServiceResultException(StatusCodes.BadRequestInterrupted); + } + } + + // return the response. + lock (m_lock) + { + if (m_error != null && throwOnError) + { + throw new ServiceResultException(m_error); + } + + return m_response; + } + } + /// /// Stores additional state information associated with the operation. /// @@ -313,14 +414,18 @@ protected virtual bool InternalComplete(bool doNotBlock, object result) { m_event.Set(); } + + if (m_tcs != null) + { + m_tcs.TrySetResult(true); + } } if (m_callback != null) { if (doNotBlock) { - Task.Run(() => - { + Task.Run(() => { m_callback(this); }); } @@ -342,12 +447,13 @@ protected virtual bool InternalComplete(bool doNotBlock, object result) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private AsyncCallback m_callback; private object m_asyncState; private bool m_synchronous; private bool m_completed; private ManualResetEvent m_event; + private TaskCompletionSource m_tcs; private T m_response; private ServiceResult m_error; private Timer m_timer; diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelQuotas.cs b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelQuotas.cs index a216f5ddb..06bfd032b 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelQuotas.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelQuotas.cs @@ -166,7 +166,7 @@ public int SecurityTokenLifetime #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private int m_maxMessageSize; private int m_maxBufferSize; private int m_channelLifetime; diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs index e980666c3..99af162e5 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs @@ -278,9 +278,8 @@ private void OnCleanup(object state) } // get reason for cleanup. - ServiceResult reason = state as ServiceResult; - if (reason == null) + if (!(state is ServiceResult reason)) { reason = new ServiceResult(StatusCodes.BadTimeout); } diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageSocket.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageSocket.cs index 42115b607..d34b5d332 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageSocket.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageSocket.cs @@ -624,9 +624,9 @@ private ServiceResult DoReadComplete(SocketAsyncEventArgs e) return ServiceResult.Create( StatusCodes.BadTcpMessageTooLarge, - "Messages size {1} bytes is too large for buffer of size {0}.", - m_receiveBufferSize, - m_incomingMessageSize); + "Messages size {0} bytes is too large for buffer of size {1}.", + m_incomingMessageSize, + m_receiveBufferSize); } // set up buffer for reading the message body. @@ -818,7 +818,7 @@ private void OnSocketConnected(object sender, SocketAsyncEventArgs args) } args.Dispose(); } -#endregion + #endregion #region Write Handling /// @@ -826,8 +826,7 @@ private void OnSocketConnected(object sender, SocketAsyncEventArgs args) /// public bool SendAsync(IMessageSocketAsyncEventArgs args) { - TcpMessageSocketAsyncEventArgs eventArgs = args as TcpMessageSocketAsyncEventArgs; - if (eventArgs == null) + if (!(args is TcpMessageSocketAsyncEventArgs eventArgs)) { throw new ArgumentNullException(nameof(args)); } diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs index 037a07eb2..78c314720 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs @@ -48,7 +48,7 @@ public static class TcpMessageType /// A chunk for a generic message. /// public const uint MessageIntermediate = Message | Intermediate; - + /// /// A chunk for a generic message. /// @@ -124,9 +124,9 @@ public static bool IsValid(uint messageType) case ReverseHello: case Acknowledge: case Error: - { - return true; - } + { + return true; + } } if (((messageType & ChunkTypeMask) != Final) && ((messageType & ChunkTypeMask) != Intermediate)) @@ -139,14 +139,14 @@ public static bool IsValid(uint messageType) case Message: case Open: case Close: - { - break; - } + { + break; + } default: - { - return false; - } + { + return false; + } } return true; diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs index fc8930a47..cef486af1 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs @@ -122,9 +122,7 @@ public IAsyncResult BeginReverseConnect(uint channelId, Uri endpointUrl, AsyncCa /// public void EndReverseConnect(IAsyncResult result) { - var ar = result as ReverseConnectAsyncResult; - - if (ar == null) + if (!(result is ReverseConnectAsyncResult ar)) { throw new ArgumentException("EndReverseConnect is called with invalid IAsyncResult.", nameof(result)); } @@ -312,9 +310,7 @@ protected override bool HandleIncomingMessage(uint messageType, ArraySegment private void OnChannelReconnected(object state) { - SortedDictionary responses = state as SortedDictionary; - - if (responses == null) + if (!(state is SortedDictionary responses)) { return; } @@ -516,7 +512,6 @@ private bool ProcessOpenSecureChannelRequest(uint messageType, ArraySegment messageC chunksToProcess = GetSavedChunks(requestId, messageBody, true); // decode the request. - IServiceRequest request = BinaryDecoder.DecodeMessage(new ArraySegmentStream(chunksToProcess), null, Quotas.MessageContext) as IServiceRequest; - if (request == null) + if (!(BinaryDecoder.DecodeMessage(new ArraySegmentStream(chunksToProcess), null, Quotas.MessageContext) is IServiceRequest request)) { SendServiceFault(token, requestId, ServiceResult.Create(StatusCodes.BadStructureMissing, "Could not parse request body.")); return true; diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs index b6127f59c..6eef9b449 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs @@ -474,9 +474,7 @@ private void OnAccept(object sender, SocketAsyncEventArgs e) repeatAccept = false; lock (m_lock) { - Socket listeningSocket = e.UserToken as Socket; - - if (listeningSocket == null) + if (!(e.UserToken is Socket listeningSocket)) { Utils.LogError("OnAccept: Listensocket was null."); e.Dispose(); @@ -710,7 +708,7 @@ private void SetUri(Uri baseAddress, string relativeAddress) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private string m_listenerId; private Uri m_uri; private EndpointDescriptionCollection m_descriptions; diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs index 273dc0196..8f019c511 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs @@ -216,25 +216,25 @@ protected int GetPlainTextBlockSize(X509Certificate2 receiverCertificate) case SecurityPolicies.Basic256: case SecurityPolicies.Basic256Sha256: case SecurityPolicies.Aes128_Sha256_RsaOaep: - { - return RsaUtils.GetPlainTextBlockSize(receiverCertificate, RsaUtils.Padding.OaepSHA1); - } + { + return RsaUtils.GetPlainTextBlockSize(receiverCertificate, RsaUtils.Padding.OaepSHA1); + } case SecurityPolicies.Aes256_Sha256_RsaPss: - { - return RsaUtils.GetPlainTextBlockSize(receiverCertificate, RsaUtils.Padding.OaepSHA256); - } + { + return RsaUtils.GetPlainTextBlockSize(receiverCertificate, RsaUtils.Padding.OaepSHA256); + } case SecurityPolicies.Basic128Rsa15: - { - return RsaUtils.GetPlainTextBlockSize(receiverCertificate, RsaUtils.Padding.Pkcs1); - } + { + return RsaUtils.GetPlainTextBlockSize(receiverCertificate, RsaUtils.Padding.Pkcs1); + } default: case SecurityPolicies.None: - { - return 1; - } + { + return 1; + } } } @@ -248,25 +248,25 @@ protected int GetCipherTextBlockSize(X509Certificate2 receiverCertificate) case SecurityPolicies.Basic256: case SecurityPolicies.Basic256Sha256: case SecurityPolicies.Aes128_Sha256_RsaOaep: - { - return RsaUtils.GetCipherTextBlockSize(receiverCertificate, RsaUtils.Padding.OaepSHA1); - } + { + return RsaUtils.GetCipherTextBlockSize(receiverCertificate, RsaUtils.Padding.OaepSHA1); + } case SecurityPolicies.Aes256_Sha256_RsaPss: - { - return RsaUtils.GetCipherTextBlockSize(receiverCertificate, RsaUtils.Padding.OaepSHA256); - } + { + return RsaUtils.GetCipherTextBlockSize(receiverCertificate, RsaUtils.Padding.OaepSHA256); + } case SecurityPolicies.Basic128Rsa15: - { - return RsaUtils.GetCipherTextBlockSize(receiverCertificate, RsaUtils.Padding.Pkcs1); - } + { + return RsaUtils.GetCipherTextBlockSize(receiverCertificate, RsaUtils.Padding.Pkcs1); + } default: case SecurityPolicies.None: - { - return 1; - } + { + return 1; + } } } @@ -284,7 +284,7 @@ protected int GetAsymmetricHeaderSize( if (securityPolicyUri != null) { - headerSize += new UTF8Encoding().GetByteCount(securityPolicyUri); + headerSize += Encoding.UTF8.GetByteCount(securityPolicyUri); } headerSize += TcpMessageLimits.StringLengthSize; @@ -323,7 +323,7 @@ protected int GetAsymmetricHeaderSize( if (securityPolicyUri != null) { - headerSize += new UTF8Encoding().GetByteCount(securityPolicyUri); + headerSize += Encoding.UTF8.GetByteCount(securityPolicyUri); } headerSize += TcpMessageLimits.StringLengthSize; @@ -359,15 +359,15 @@ protected int GetAsymmetricSignatureSize(X509Certificate2 senderCertificate) case SecurityPolicies.Basic256Sha256: case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Aes256_Sha256_RsaPss: - { - return RsaUtils.GetSignatureLength(senderCertificate); - } + { + return RsaUtils.GetSignatureLength(senderCertificate); + } default: case SecurityPolicies.None: - { - return 0; - } + { + return 0; + } } } @@ -473,7 +473,7 @@ private int GetMaxSenderCertificateSize(X509Certificate2 senderCertificate, stri if (securityPolicyUri != null) { - occupiedSize += new UTF8Encoding().GetByteCount(securityPolicyUri); //security policy uri size + occupiedSize += Encoding.UTF8.GetByteCount(securityPolicyUri); //security policy uri size } occupiedSize += TcpMessageLimits.StringLengthSize; //SenderCertificateLength @@ -693,7 +693,7 @@ protected BufferCollection WriteAsymmetricMessage( } catch (Exception ex) { - throw new Exception("Could not write async message", ex); + throw new ServiceResultException("Could not write async message", ex); } finally { @@ -890,9 +890,7 @@ protected ArraySegment ReadAsymmetricMessage( // validate the sender certificate. if (senderCertificate != null && Quotas.CertificateValidator != null && securityPolicyUri != SecurityPolicies.None) { - CertificateValidator certificateValidator = Quotas.CertificateValidator as CertificateValidator; - - if (certificateValidator != null) + if (Quotas.CertificateValidator is CertificateValidator certificateValidator) { certificateValidator.Validate(senderCertificateChain); } @@ -1053,26 +1051,26 @@ protected byte[] Sign( { default: case SecurityPolicies.None: - { - return null; - } + { + return null; + } case SecurityPolicies.Basic256: case SecurityPolicies.Basic128Rsa15: - { - return Rsa_Sign(dataToSign, senderCertificate, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); - } + { + return Rsa_Sign(dataToSign, senderCertificate, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); + } case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Basic256Sha256: - { - return Rsa_Sign(dataToSign, senderCertificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - } + { + return Rsa_Sign(dataToSign, senderCertificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } case SecurityPolicies.Aes256_Sha256_RsaPss: - { - return Rsa_Sign(dataToSign, senderCertificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); - } + { + return Rsa_Sign(dataToSign, senderCertificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + } } } @@ -1093,31 +1091,31 @@ protected bool Verify( switch (SecurityPolicyUri) { case SecurityPolicies.None: - { - return true; - } + { + return true; + } case SecurityPolicies.Basic128Rsa15: case SecurityPolicies.Basic256: - { - return Rsa_Verify(dataToVerify, signature, senderCertificate, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); - } + { + return Rsa_Verify(dataToVerify, signature, senderCertificate, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); + } case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Basic256Sha256: - { - return Rsa_Verify(dataToVerify, signature, senderCertificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - } + { + return Rsa_Verify(dataToVerify, signature, senderCertificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } case SecurityPolicies.Aes256_Sha256_RsaPss: - { - return Rsa_Verify(dataToVerify, signature, senderCertificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); - } + { + return Rsa_Verify(dataToVerify, signature, senderCertificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + } default: - { - return false; - } + { + return false; + } } } @@ -1138,31 +1136,31 @@ protected ArraySegment Encrypt( { default: case SecurityPolicies.None: - { - byte[] encryptedBuffer = BufferManager.TakeBuffer(SendBufferSize, "Encrypt"); + { + byte[] encryptedBuffer = BufferManager.TakeBuffer(SendBufferSize, "Encrypt"); - Array.Copy(headerToCopy.Array, headerToCopy.Offset, encryptedBuffer, 0, headerToCopy.Count); - Array.Copy(dataToEncrypt.Array, dataToEncrypt.Offset, encryptedBuffer, headerToCopy.Count, dataToEncrypt.Count); + Array.Copy(headerToCopy.Array, headerToCopy.Offset, encryptedBuffer, 0, headerToCopy.Count); + Array.Copy(dataToEncrypt.Array, dataToEncrypt.Offset, encryptedBuffer, headerToCopy.Count, dataToEncrypt.Count); - return new ArraySegment(encryptedBuffer, 0, dataToEncrypt.Count + headerToCopy.Count); - } + return new ArraySegment(encryptedBuffer, 0, dataToEncrypt.Count + headerToCopy.Count); + } case SecurityPolicies.Basic256: case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Basic256Sha256: - { - return Rsa_Encrypt(dataToEncrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA1); - } + { + return Rsa_Encrypt(dataToEncrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA1); + } case SecurityPolicies.Aes256_Sha256_RsaPss: - { - return Rsa_Encrypt(dataToEncrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA256); - } + { + return Rsa_Encrypt(dataToEncrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA256); + } case SecurityPolicies.Basic128Rsa15: - { - return Rsa_Encrypt(dataToEncrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.Pkcs1); - } + { + return Rsa_Encrypt(dataToEncrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.Pkcs1); + } } } @@ -1182,31 +1180,31 @@ protected ArraySegment Decrypt( { default: case SecurityPolicies.None: - { - byte[] decryptedBuffer = BufferManager.TakeBuffer(SendBufferSize, "Decrypt"); + { + byte[] decryptedBuffer = BufferManager.TakeBuffer(SendBufferSize, "Decrypt"); - Array.Copy(headerToCopy.Array, headerToCopy.Offset, decryptedBuffer, 0, headerToCopy.Count); - Array.Copy(dataToDecrypt.Array, dataToDecrypt.Offset, decryptedBuffer, headerToCopy.Count, dataToDecrypt.Count); + Array.Copy(headerToCopy.Array, headerToCopy.Offset, decryptedBuffer, 0, headerToCopy.Count); + Array.Copy(dataToDecrypt.Array, dataToDecrypt.Offset, decryptedBuffer, headerToCopy.Count, dataToDecrypt.Count); - return new ArraySegment(decryptedBuffer, 0, dataToDecrypt.Count + headerToCopy.Count); - } + return new ArraySegment(decryptedBuffer, 0, dataToDecrypt.Count + headerToCopy.Count); + } case SecurityPolicies.Basic256: case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Basic256Sha256: - { - return Rsa_Decrypt(dataToDecrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA1); - } + { + return Rsa_Decrypt(dataToDecrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA1); + } case SecurityPolicies.Aes256_Sha256_RsaPss: - { - return Rsa_Decrypt(dataToDecrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA256); - } + { + return Rsa_Decrypt(dataToDecrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA256); + } case SecurityPolicies.Basic128Rsa15: - { - return Rsa_Decrypt(dataToDecrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.Pkcs1); - } + { + return Rsa_Decrypt(dataToDecrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.Pkcs1); + } } } #endregion diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Rsa.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Rsa.cs index f609a5231..527a270d6 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Rsa.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Rsa.cs @@ -67,7 +67,7 @@ private static bool Rsa_Verify( // verify signature. if (!rsa.VerifyData(dataToVerify.Array, dataToVerify.Offset, dataToVerify.Count, signature, algorithm, padding)) { - string messageType = new UTF8Encoding().GetString(dataToVerify.Array, dataToVerify.Offset, 4); + string messageType = Encoding.UTF8.GetString(dataToVerify.Array, dataToVerify.Offset, 4); int messageLength = BitConverter.ToInt32(dataToVerify.Array, dataToVerify.Offset + 4); string actualSignature = Utils.ToHexString(signature); Utils.LogError("Could not validate signature."); diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs index 5335ab0e6..7b5bbf681 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs @@ -277,9 +277,8 @@ protected BufferCollection WriteSymmetricMessage( maxPayloadSize); // check for encodeable body. - IEncodeable encodeable = messageBody as IEncodeable; - if (encodeable != null) + if (messageBody is IEncodeable encodeable) { // debug code used to verify that message aborts are handled correctly. // int maxMessageSize = Quotas.MessageContext.MaxMessageSize; @@ -736,7 +735,7 @@ private bool SymmetricVerify( { if (computedSignature[ii] != signature[ii]) { - string messageType = new UTF8Encoding().GetString(dataToVerify.Array, dataToVerify.Offset, 4); + string messageType = Encoding.UTF8.GetString(dataToVerify.Array, dataToVerify.Offset, 4); int messageLength = BitConverter.ToInt32(dataToVerify.Array, dataToVerify.Offset + 4); string expectedSignature = Utils.ToHexString(computedSignature); string actualSignature = Utils.ToHexString(signature); diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs index f5c1c0ae5..593c0ac61 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs @@ -82,7 +82,7 @@ public UaSCUaBinaryChannel( } } - if (new UTF8Encoding().GetByteCount(securityPolicyUri) > TcpMessageLimits.MaxSecurityPolicyUriSize) + if (Encoding.UTF8.GetByteCount(securityPolicyUri) > TcpMessageLimits.MaxSecurityPolicyUriSize) { throw new ArgumentException( Utils.Format("UTF-8 form of the security policy URI may not be more than {0} bytes.", TcpMessageLimits.MaxSecurityPolicyUriSize), @@ -540,11 +540,9 @@ protected static void WriteErrorMessageBody(BinaryEncoder encoder, ServiceResult // check that length is not exceeded. if (reason != null) { - UTF8Encoding encoding = new UTF8Encoding(); - - if (encoding.GetByteCount(reason) > TcpMessageLimits.MaxErrorReasonLength) + if (Encoding.UTF8.GetByteCount(reason) > TcpMessageLimits.MaxErrorReasonLength) { - reason = reason.Substring(0, TcpMessageLimits.MaxErrorReasonLength / encoding.GetMaxByteCount(1)); + reason = reason.Substring(0, TcpMessageLimits.MaxErrorReasonLength / Encoding.UTF8.GetMaxByteCount(1)); } } @@ -574,7 +572,12 @@ protected static ServiceResult ReadErrorMessageBody(BinaryDecoder decoder) reasonBytes[ii] = decoder.ReadByte(null); } - reason = new UTF8Encoding().GetString(reasonBytes, 0, reasonLength); + reason = Encoding.UTF8.GetString(reasonBytes, 0, reasonLength); + } + + if (reason == null) + { + reason = new ServiceResult(statusCode).ToString(); } return ServiceResult.Create(statusCode, "Error received from remote host: {0}", reason); @@ -761,7 +764,6 @@ protected uint ChannelId m_globalChannelId = Utils.Format("{0}-{1}", m_contextId, m_channelId); } } - #endregion #region WriteOperation Class @@ -827,7 +829,7 @@ protected static int CalculateChunkCount(int messageSize, int bufferSize) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private IMessageSocket m_socket; private BufferManager m_bufferManager; private ChannelQuotas m_quotas; diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs index a3e1bd860..90159659b 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs @@ -159,7 +159,6 @@ public IAsyncResult BeginConnect(Uri url, int timeout, AsyncCallback callback, o using (var cts = new CancellationTokenSource(timeout)) { await (Socket?.BeginConnect(m_via, m_ConnectCallback, operation, cts.Token) ?? Task.FromResult(false)).ConfigureAwait(false); - } }); } @@ -172,8 +171,7 @@ public IAsyncResult BeginConnect(Uri url, int timeout, AsyncCallback callback, o /// public void EndConnect(IAsyncResult result) { - var operation = result as WriteOperation; - if (operation == null) throw new ArgumentNullException(nameof(result)); + if (!(result is WriteOperation operation)) throw new ArgumentNullException(nameof(result)); try { @@ -191,38 +189,77 @@ public void EndConnect(IAsyncResult result) } } + /// + /// Finishes a connect operation. + /// + public async Task EndConnectAsync(IAsyncResult result, CancellationToken ct = default) + { + if (!(result is WriteOperation operation)) throw new ArgumentNullException(nameof(result)); + + try + { + await operation.EndAsync(Int32.MaxValue, true, ct).ConfigureAwait(false); + Utils.LogInfo("CLIENTCHANNEL SOCKET CONNECTED: {0:X8}, ChannelId={1}", Socket.Handle, ChannelId); + } + catch (Exception e) + { + Shutdown(ServiceResult.Create(e, StatusCodes.BadTcpInternalError, "Fatal error during connect.")); + throw; + } + finally + { + OperationCompleted(operation); + } + } + /// /// Closes a connection with the server. /// - public void Close(int timeout) + public async Task CloseAsync(int timeout, CancellationToken ct = default) { - WriteOperation operation = null; + WriteOperation operation = InternalClose(timeout); - lock (DataLock) + // wait for the close to succeed. + if (operation != null) { - // nothing to do if the connection is already closed. - if (State == TcpChannelState.Closed) + try { - return; + await operation.EndAsync(timeout, true, ct).ConfigureAwait(false); } - - // check if a handshake is in progress. - if (m_handshakeOperation != null && !m_handshakeOperation.IsCompleted) + catch (ServiceResultException e) { - m_handshakeOperation.Fault(ServiceResult.Create(StatusCodes.BadConnectionClosed, "Channel was closed by the user.")); - } - - Utils.LogTrace("ChannelId {0}: Close", ChannelId); + switch (e.StatusCode) + { + case StatusCodes.BadRequestInterrupted: + case StatusCodes.BadSecureChannelClosed: + { + break; + } - // attempt a graceful shutdown. - if (State == TcpChannelState.Open) + default: + { + Utils.LogWarning(e, "ChannelId {0}: Could not gracefully close the channel. Reason={1}", ChannelId, e.Result.StatusCode); + break; + } + } + } + catch (Exception e) { - State = TcpChannelState.Closing; - operation = BeginOperation(timeout, null, null); - SendCloseSecureChannelRequest(operation); + Utils.LogError(e, "ChannelId {0}: Could not gracefully close the channel.", ChannelId); } } + // shutdown. + Shutdown(StatusCodes.BadConnectionClosed); + } + + /// + /// Closes a connection with the server. + /// + public void Close(int timeout) + { + WriteOperation operation = InternalClose(timeout); + // wait for the close to succeed. if (operation != null) { @@ -325,9 +362,7 @@ public IAsyncResult BeginSendRequest(IServiceRequest request, int timeout, Async /// public IServiceResponse EndSendRequest(IAsyncResult result) { - WriteOperation operation = result as WriteOperation; - - if (operation == null) + if (!(result is WriteOperation operation)) { throw new ArgumentNullException(nameof(result)); } @@ -343,6 +378,28 @@ public IServiceResponse EndSendRequest(IAsyncResult result) return operation.MessageBody as IServiceResponse; } + + /// + /// Returns the response to a previously sent request. + /// + public async Task EndSendRequestAsync(IAsyncResult result, CancellationToken ct) + { + if (!(result is WriteOperation operation)) + { + throw new ArgumentNullException(nameof(result)); + } + + try + { + await operation.EndAsync(Int32.MaxValue, true, ct).ConfigureAwait(false); + } + finally + { + OperationCompleted(operation); + } + + return operation.MessageBody as IServiceResponse; + } #endregion #region Connect/Reconnect Sequence @@ -368,7 +425,7 @@ private void SendHelloMessage(WriteOperation operation) encoder.WriteUInt32(null, (uint)MaxResponseMessageSize); encoder.WriteUInt32(null, (uint)MaxResponseChunkCount); - byte[] endpointUrl = new UTF8Encoding().GetBytes(m_url.ToString()); + byte[] endpointUrl = Encoding.UTF8.GetBytes(m_url.ToString()); if (endpointUrl.Length > TcpMessageLimits.MaxEndpointUrlLength) { @@ -602,9 +659,8 @@ private bool ProcessOpenSecureChannelResponse(uint messageType, ArraySegment private IServiceResponse ParseResponse(BufferCollection chunksToProcess) { - IServiceResponse response = BinaryDecoder.DecodeMessage(new ArraySegmentStream(chunksToProcess), null, Quotas.MessageContext) as IServiceResponse; - if (response == null) + if (!(BinaryDecoder.DecodeMessage(new ArraySegmentStream(chunksToProcess), null, Quotas.MessageContext) is IServiceResponse response)) { throw ServiceResultException.Create(StatusCodes.BadStructureMissing, "Could not parse response body."); } @@ -1081,8 +1134,6 @@ private void ForceReconnect(ServiceResult reason) return; } - Utils.LogWarning("ChannelId {0}: Force reconnect reason={1}", Id, reason); - // check if reconnects are disabled. if (State == TcpChannelState.Closing || m_waitBetweenReconnects == Timeout.Infinite) { @@ -1090,6 +1141,8 @@ private void ForceReconnect(ServiceResult reason) return; } + Utils.LogWarning("ChannelId {0}: Force reconnect reason={1}", Id, reason); + // cancel all requests. List operations = new List(m_requests.Values); @@ -1275,6 +1328,37 @@ private void OnConnectOnDemandComplete(object state) m_queuedOperations = null; } } + + private WriteOperation InternalClose(int timeout) + { + WriteOperation operation = null; + lock (DataLock) + { + // nothing to do if the connection is already closed. + if (State == TcpChannelState.Closed) + { + return null; + } + + // check if a handshake is in progress. + if (m_handshakeOperation != null && !m_handshakeOperation.IsCompleted) + { + m_handshakeOperation.Fault(ServiceResult.Create(StatusCodes.BadConnectionClosed, "Channel was closed by the user.")); + } + + Utils.LogTrace("ChannelId {0}: Close", ChannelId); + + // attempt a graceful shutdown. + if (State == TcpChannelState.Open) + { + State = TcpChannelState.Closing; + operation = BeginOperation(timeout, null, null); + SendCloseSecureChannelRequest(operation); + } + } + + return operation; + } #endregion #region Message Processing diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs index 583633045..992d0772c 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs @@ -23,6 +23,8 @@ namespace Opc.Ua.Bindings /// public class UaSCUaBinaryTransportChannel : ITransportChannel, IMessageSocketChannel { + private const int kChannelCloseDefault = 1_000; + #region Constructors /// /// Create a transport channel from a message socket factory. @@ -223,7 +225,7 @@ public void Reconnect(ITransportWaitingConnection connection) { try { - channel.Close(1000); + channel.Close(kChannelCloseDefault); } catch (Exception e) { @@ -277,13 +279,34 @@ public void Close() { if (m_channel != null) { - m_channel.Close(1000); + m_channel.Close(kChannelCloseDefault); m_channel = null; } } } } + /// + /// Closes the secure channel (async). + /// + /// Thrown if any communication error occurs. + public async Task CloseAsync(CancellationToken ct) + { + UaSCUaBinaryClientChannel channel = null; + lock (m_lock) + { + if (m_channel != null) + { + channel = m_channel; + m_channel = null; + } + } + if (channel != null) + { + await channel.CloseAsync(kChannelCloseDefault, ct).ConfigureAwait(false); + } + } + /// /// Begins an asynchronous operation to close the secure channel. /// @@ -331,7 +354,8 @@ public IServiceResponse SendRequest(IServiceRequest request) /// Thrown if any communication error occurs. public Task SendRequestAsync(IServiceRequest request, CancellationToken ct) { - return Task.Factory.FromAsync(BeginSendRequest(request, null, null), EndSendRequest); + var operation = BeginSendRequest(request, null, null); + return EndSendRequestAsync(operation, ct); } /// @@ -384,6 +408,26 @@ public IServiceResponse EndSendRequest(IAsyncResult result) return channel.EndSendRequest(result); } + /// + /// Completes an asynchronous operation to send a request over the secure channel. + /// + /// The result returned from the BeginSendRequest call. + /// + /// + /// Thrown if any communication error occurs. + /// + public Task EndSendRequestAsync(IAsyncResult result, CancellationToken ct) + { + UaSCUaBinaryClientChannel channel = m_channel; + + if (channel == null) + { + throw ServiceResultException.Create(StatusCodes.BadSecureChannelClosed, "Channel has been closed."); + } + + return channel.EndSendRequestAsync(result, ct); + } + /// /// Saves the settings so the channel can be opened later. /// @@ -457,7 +501,7 @@ private void CreateChannel(ITransportWaitingConnection connection = null) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private Uri m_url; private int m_operationTimeout; private TransportChannelSettings m_settings; diff --git a/Stack/Opc.Ua.Core/Stack/Transport/AsyncResultBase.cs b/Stack/Opc.Ua.Core/Stack/Transport/AsyncResultBase.cs index 86aff8d7a..cb21da1ee 100644 --- a/Stack/Opc.Ua.Core/Stack/Transport/AsyncResultBase.cs +++ b/Stack/Opc.Ua.Core/Stack/Transport/AsyncResultBase.cs @@ -125,9 +125,7 @@ public CancellationToken CancellationToken /// The result object returned from the Begin method. public static void WaitForComplete(IAsyncResult ar) { - AsyncResultBase result = ar as AsyncResultBase; - - if (result == null) + if (!(ar is AsyncResultBase result)) { throw new ArgumentException("IAsyncResult passed to call is not an instance of AsyncResultBase."); } diff --git a/Stack/Opc.Ua.Core/Stack/Transport/ITransportChannel.cs b/Stack/Opc.Ua.Core/Stack/Transport/ITransportChannel.cs index 9b2155028..cc02a3503 100644 --- a/Stack/Opc.Ua.Core/Stack/Transport/ITransportChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Transport/ITransportChannel.cs @@ -138,6 +138,12 @@ IAsyncResult BeginOpen( /// Thrown if any communication error occurs. void Close(); + /// + /// Closes the secure channel (async). + /// + /// Thrown if any communication error occurs. + Task CloseAsync(CancellationToken ct); + /// /// Begins an asynchronous operation to close the secure channel. /// @@ -191,6 +197,16 @@ IAsyncResult BeginOpen( /// Thrown if any communication error occurs. /// IServiceResponse EndSendRequest(IAsyncResult result); + + /// + /// Completes an asynchronous operation to send a request over the secure channel. + /// Awaitable version + /// + /// The result returned from the BeginSendRequest call. + /// The cancellation token. + /// Thrown if any communication error occurs. + /// + Task EndSendRequestAsync(IAsyncResult result, CancellationToken ct); } /// diff --git a/Stack/Opc.Ua.Core/Stack/Types/Argument.cs b/Stack/Opc.Ua.Core/Stack/Types/Argument.cs index 25bc46693..5dfae0267 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/Argument.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/Argument.cs @@ -17,10 +17,10 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. namespace Opc.Ua { - /// - /// The Argument class. - /// - public partial class Argument + /// + /// The Argument class. + /// + public partial class Argument { #region Public Properties /// @@ -40,14 +40,14 @@ public Argument(string name, NodeId dataType, int valueRank, string description) /// The value for the argument. /// public object Value - { - get { return m_value; } - set { m_value = value; } - } - #endregion + { + get { return m_value; } + set { m_value = value; } + } + #endregion - #region Private Fields - private object m_value; - #endregion - } + #region Private Fields + private object m_value; + #endregion + } } diff --git a/Stack/Opc.Ua.Core/Stack/Types/BrowseDescription.cs b/Stack/Opc.Ua.Core/Stack/Types/BrowseDescription.cs index ae48a546a..4b76fc88b 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/BrowseDescription.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/BrowseDescription.cs @@ -26,11 +26,11 @@ public partial class BrowseDescription /// public object Handle { - get { return m_handle; } + get { return m_handle; } set { m_handle = value; } } #endregion - + #region Private Fields private object m_handle; #endregion diff --git a/Stack/Opc.Ua.Core/Stack/Types/BrowsePath.cs b/Stack/Opc.Ua.Core/Stack/Types/BrowsePath.cs index 5575082a9..913241a62 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/BrowsePath.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/BrowsePath.cs @@ -16,7 +16,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.Security.Cryptography.X509Certificates; namespace Opc.Ua -{ +{ #region BrowsePath Class public partial class BrowsePath { @@ -26,11 +26,11 @@ public partial class BrowsePath /// public object Handle { - get { return m_handle; } + get { return m_handle; } set { m_handle = value; } } #endregion - + #region Private Fields private object m_handle; #endregion diff --git a/Stack/Opc.Ua.Core/Stack/Types/CallMethodRequest.cs b/Stack/Opc.Ua.Core/Stack/Types/CallMethodRequest.cs index 7da8d64f1..864b71adc 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/CallMethodRequest.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/CallMethodRequest.cs @@ -17,9 +17,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. namespace Opc.Ua { - /// - /// The description of a value to write. - /// + /// + /// The description of a value to write. + /// public partial class CallMethodRequest { #region Supporting Properties and Methods @@ -28,7 +28,7 @@ public partial class CallMethodRequest /// public object Handle { - get { return m_handle; } + get { return m_handle; } set { m_handle = value; } } @@ -37,11 +37,11 @@ public object Handle /// public bool Processed { - get { return m_processed; } + get { return m_processed; } set { m_processed = value; } } #endregion - + #region Private Fields private object m_handle; private bool m_processed; diff --git a/Stack/Opc.Ua.Core/Stack/Types/ContentFilter.Evaluate.cs b/Stack/Opc.Ua.Core/Stack/Types/ContentFilter.Evaluate.cs index d54b2b64f..bc25a7769 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/ContentFilter.Evaluate.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/ContentFilter.Evaluate.cs @@ -175,9 +175,8 @@ private FilterOperand[] GetOperands(ContentFilterElement element, int expectedCo throw new ServiceResultException(StatusCodes.BadUnexpectedError, "FilterOperand is null."); } - FilterOperand operand = extension.Body as FilterOperand; - if (operand == null) + if (!(extension.Body is FilterOperand operand)) { throw new ServiceResultException(StatusCodes.BadUnexpectedError, "FilterOperand is not supported."); } @@ -224,17 +223,15 @@ private Tuple GetBitwiseOperands(FilterContext context, IFilterT private object GetValue(FilterContext context, FilterOperand operand, IFilterTarget target) { // return the contained value for literal operands. - LiteralOperand literal = operand as LiteralOperand; - if (literal != null) + if (operand is LiteralOperand literal) { return literal.Value.Value; } // must query the filter target for simple attribute operands. - SimpleAttributeOperand simpleAttribute = operand as SimpleAttributeOperand; - if (simpleAttribute != null) + if (operand is SimpleAttributeOperand simpleAttribute) { return target.GetAttributeValue( context, @@ -245,14 +242,12 @@ private object GetValue(FilterContext context, FilterOperand operand, IFilterTar } // must query the filter target for attribute operands. - AttributeOperand attribute = operand as AttributeOperand; - if (attribute != null) + if (operand is AttributeOperand attribute) { // AttributeOperands only supported in advanced filter targets. - IAdvancedFilterTarget advancedTarget = target as IAdvancedFilterTarget; - if (advancedTarget == null) + if (!(target is IAdvancedFilterTarget advancedTarget)) { return false; } @@ -266,9 +261,8 @@ private object GetValue(FilterContext context, FilterOperand operand, IFilterTar } // recursively evaluate element operands. - ElementOperand element = operand as ElementOperand; - if (element != null) + if (operand is ElementOperand element) { return Evaluate(context, target, (int)element.Index); } @@ -483,9 +477,8 @@ private static bool isIntegerType(BuiltInType aType) private static object ToBoolean(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { bool[] output = new bool[array.Length]; @@ -532,9 +525,8 @@ private static object ToBoolean(object value, BuiltInType sourceType) private static object ToSByte(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { sbyte[] output = new sbyte[array.Length]; @@ -581,9 +573,8 @@ private static object ToSByte(object value, BuiltInType sourceType) private static object ToByte(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { throw new NotImplementedException("Arrays of Byte not supported. Use ByteString instead."); } @@ -623,9 +614,8 @@ private static object ToByte(object value, BuiltInType sourceType) private static object ToInt16(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { short[] output = new short[array.Length]; @@ -672,9 +662,8 @@ private static object ToInt16(object value, BuiltInType sourceType) private static object ToUInt16(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { ushort[] output = new ushort[array.Length]; @@ -727,9 +716,8 @@ private static object ToUInt16(object value, BuiltInType sourceType) private static object ToInt32(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { int[] output = new int[array.Length]; @@ -781,9 +769,8 @@ private static object ToInt32(object value, BuiltInType sourceType) private static object ToUInt32(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { uint[] output = new uint[array.Length]; @@ -835,9 +822,8 @@ private static object ToUInt32(object value, BuiltInType sourceType) private static object ToInt64(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { long[] output = new long[array.Length]; @@ -889,9 +875,8 @@ private static object ToInt64(object value, BuiltInType sourceType) private static object ToUInt64(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { ulong[] output = new ulong[array.Length]; @@ -943,9 +928,8 @@ private static object ToUInt64(object value, BuiltInType sourceType) private static object ToFloat(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { float[] output = new float[array.Length]; @@ -992,9 +976,8 @@ private static object ToFloat(object value, BuiltInType sourceType) private static object ToDouble(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { double[] output = new double[array.Length]; @@ -1041,9 +1024,8 @@ private static object ToDouble(object value, BuiltInType sourceType) private static object ToString(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { String[] output = new String[array.Length]; @@ -1159,9 +1141,8 @@ private static object ToString(object value, BuiltInType sourceType) private static object ToDateTime(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { DateTime[] output = new DateTime[array.Length]; @@ -1197,9 +1178,8 @@ private static object ToDateTime(object value, BuiltInType sourceType) private static object ToGuid(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { Guid[] output = new Guid[array.Length]; @@ -1240,9 +1220,8 @@ private static object ToGuid(object value, BuiltInType sourceType) private static object ToByteString(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { byte[][] output = new byte[array.Length][]; @@ -1278,9 +1257,8 @@ private static object ToByteString(object value, BuiltInType sourceType) private static object ToNodeId(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { NodeId[] output = new NodeId[array.Length]; @@ -1321,9 +1299,8 @@ private static object ToNodeId(object value, BuiltInType sourceType) private static object ToExpandedNodeId(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { ExpandedNodeId[] output = new ExpandedNodeId[array.Length]; @@ -1364,9 +1341,8 @@ private static object ToExpandedNodeId(object value, BuiltInType sourceType) private static object ToStatusCode(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { StatusCode[] output = new StatusCode[array.Length]; @@ -1424,9 +1400,8 @@ private static object ToStatusCode(object value, BuiltInType sourceType) private static object ToQualifiedName(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { QualifiedName[] output = new QualifiedName[array.Length]; @@ -1462,9 +1437,8 @@ private static object ToQualifiedName(object value, BuiltInType sourceType) private static object ToLocalizedText(object value, BuiltInType sourceType) { // check for array conversions. - Array array = value as Array; - if (array != null) + if (value is Array array) { LocalizedText[] output = new LocalizedText[array.Length]; @@ -1963,9 +1937,8 @@ private bool OfType(FilterContext context, IFilterTarget target, ContentFilterEl private bool InView(FilterContext context, IFilterTarget target, ContentFilterElement element) { // views only supported in advanced filter targets. - IAdvancedFilterTarget advancedFilter = target as IAdvancedFilterTarget; - if (advancedFilter == null) + if (!(target is IAdvancedFilterTarget advancedFilter)) { return false; } @@ -2005,9 +1978,8 @@ private bool RelatedTo(FilterContext context, IFilterTarget target, ContentFilte private bool RelatedTo(FilterContext context, IFilterTarget target, ContentFilterElement element, NodeId intermediateNodeId) { // RelatedTo only supported in advanced filter targets. - IAdvancedFilterTarget advancedTarget = target as IAdvancedFilterTarget; - if (advancedTarget == null) + if (!(target is IAdvancedFilterTarget advancedTarget)) { return false; } @@ -2078,9 +2050,8 @@ private bool RelatedTo(FilterContext context, IFilterTarget target, ContentFilte NodeId targetTypeId = null; // check if elements are chained. - ElementOperand chainedOperand = operands[1] as ElementOperand; - if (chainedOperand != null) + if (operands[1] is ElementOperand chainedOperand) { if (/*chainedOperand.Index < 0 ||*/ chainedOperand.Index >= Elements.Count) { diff --git a/Stack/Opc.Ua.Core/Stack/Types/EUInformation.cs b/Stack/Opc.Ua.Core/Stack/Types/EUInformation.cs index 84d73b238..4bdad86f8 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/EUInformation.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/EUInformation.cs @@ -29,8 +29,8 @@ public EUInformation(string unitName, string namespaceUri) { Initialize(); - m_displayName = new LocalizedText(unitName); - m_description = new LocalizedText(unitName); + m_displayName = new LocalizedText(unitName); + m_description = new LocalizedText(unitName); m_namespaceUri = namespaceUri; } @@ -41,8 +41,8 @@ public EUInformation(string shortName, string longName, string namespaceUri) { Initialize(); - m_displayName = new LocalizedText(shortName); - m_description = new LocalizedText(longName); + m_displayName = new LocalizedText(shortName); + m_description = new LocalizedText(longName); m_namespaceUri = namespaceUri; } } diff --git a/Stack/Opc.Ua.Core/Stack/Types/EventFieldList.cs b/Stack/Opc.Ua.Core/Stack/Types/EventFieldList.cs index 405047b26..408cba3a6 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/EventFieldList.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/EventFieldList.cs @@ -16,12 +16,12 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.Security.Cryptography.X509Certificates; namespace Opc.Ua -{ +{ /// /// A list of event field values returned in a NotificationMessage. /// - public partial class EventFieldList - { + public partial class EventFieldList + { #region Public Properties /// /// The handle cast to a notification message. diff --git a/Stack/Opc.Ua.Core/Stack/Types/HistoryReadValueId.cs b/Stack/Opc.Ua.Core/Stack/Types/HistoryReadValueId.cs index d6f853647..efd48b319 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/HistoryReadValueId.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/HistoryReadValueId.cs @@ -17,9 +17,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. namespace Opc.Ua { - /// - /// The description of a value to read. - /// + /// + /// The description of a value to read. + /// public partial class HistoryReadValueId { #region Supporting Properties and Methods @@ -28,16 +28,16 @@ public partial class HistoryReadValueId /// public object Handle { - get { return m_handle; } + get { return m_handle; } set { m_handle = value; } } - + /// /// Whether the value has been processed. /// public bool Processed { - get { return m_processed; } + get { return m_processed; } set { m_processed = value; } } @@ -46,10 +46,10 @@ public bool Processed /// public NumericRange ParsedIndexRange { - get { return m_parsedIndexRange; } + get { return m_parsedIndexRange; } set { m_parsedIndexRange = value; } } - + /// /// Validates a read value id parameter. /// @@ -86,12 +86,12 @@ public static ServiceResult Validate(HistoryReadValueId valueId) { valueId.ParsedIndexRange = NumericRange.Empty; } - + // passed basic validation. return null; } #endregion - + #region Private Fields private object m_handle; private bool m_processed; diff --git a/Stack/Opc.Ua.Core/Stack/Types/HistoryUpdateDetails.cs b/Stack/Opc.Ua.Core/Stack/Types/HistoryUpdateDetails.cs index 78ea7c02c..c6028ff43 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/HistoryUpdateDetails.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/HistoryUpdateDetails.cs @@ -37,19 +37,19 @@ public abstract NodeId NodeId /// public object Handle { - get { return m_handle; } + get { return m_handle; } set { m_handle = value; } } - + /// /// Whether the value has been processed. /// public bool Processed { - get { return m_processed; } + get { return m_processed; } set { m_processed = value; } } - + /// /// Validates a HistoryUpdateDetails parameter. /// @@ -66,12 +66,12 @@ public static ServiceResult Validate(HistoryUpdateDetails valueId) { return StatusCodes.BadNodeIdInvalid; } - + // passed basic validation. return null; } #endregion - + #region Private Fields private object m_handle; private bool m_processed; diff --git a/Stack/Opc.Ua.Core/Stack/Types/MonitoredItemCreateRequest.cs b/Stack/Opc.Ua.Core/Stack/Types/MonitoredItemCreateRequest.cs index 7b68e9908..740bc7579 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/MonitoredItemCreateRequest.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/MonitoredItemCreateRequest.cs @@ -17,9 +17,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. namespace Opc.Ua { - /// - /// The description of a monitored item to create. - /// + /// + /// The description of a monitored item to create. + /// public partial class MonitoredItemCreateRequest { #region Supporting Properties and Methods @@ -28,7 +28,7 @@ public partial class MonitoredItemCreateRequest /// public object Handle { - get { return m_handle; } + get { return m_handle; } set { m_handle = value; } } @@ -37,7 +37,7 @@ public object Handle /// public bool Processed { - get { return m_processed; } + get { return m_processed; } set { m_processed = value; } } #endregion diff --git a/Stack/Opc.Ua.Core/Stack/Types/MonitoredItemCreateResult.cs b/Stack/Opc.Ua.Core/Stack/Types/MonitoredItemCreateResult.cs index b2fd52b16..e9df10cf6 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/MonitoredItemCreateResult.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/MonitoredItemCreateResult.cs @@ -17,9 +17,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. namespace Opc.Ua { - /// - /// The description of a monitored item to create. - /// + /// + /// The description of a monitored item to create. + /// public partial class MonitoredItemCreateResult { /// diff --git a/Stack/Opc.Ua.Core/Stack/Types/MonitoredItemModifyRequest.cs b/Stack/Opc.Ua.Core/Stack/Types/MonitoredItemModifyRequest.cs index 7c0e2db11..96d7d9c6c 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/MonitoredItemModifyRequest.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/MonitoredItemModifyRequest.cs @@ -17,9 +17,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. namespace Opc.Ua { - /// - /// The description of a monitored item to create. - /// + /// + /// The description of a monitored item to create. + /// public partial class MonitoredItemModifyRequest { #region Supporting Properties and Methods @@ -28,7 +28,7 @@ public partial class MonitoredItemModifyRequest /// public object Handle { - get { return m_handle; } + get { return m_handle; } set { m_handle = value; } } @@ -37,7 +37,7 @@ public object Handle /// public bool Processed { - get { return m_processed; } + get { return m_processed; } set { m_processed = value; } } #endregion diff --git a/Stack/Opc.Ua.Core/Stack/Types/MonitoredItemNotification.cs b/Stack/Opc.Ua.Core/Stack/Types/MonitoredItemNotification.cs index 0b86b7ed9..ba148a10a 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/MonitoredItemNotification.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/MonitoredItemNotification.cs @@ -21,7 +21,7 @@ namespace Opc.Ua /// A datachange returned in a NotificationMessage. /// public partial class MonitoredItemNotification - { + { #region Public Properties /// /// The notification message that the item belongs to. diff --git a/Stack/Opc.Ua.Core/Stack/Types/MonitoringFilter.cs b/Stack/Opc.Ua.Core/Stack/Types/MonitoringFilter.cs index fb5ba2fb1..ef0795cd3 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/MonitoringFilter.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/MonitoringFilter.cs @@ -32,17 +32,17 @@ public ServiceResult Validate() if ((int)DeadbandType < (int)Opc.Ua.DeadbandType.None || (int)DeadbandType > (int)Opc.Ua.DeadbandType.Percent) { return ServiceResult.Create( - StatusCodes.BadDeadbandFilterInvalid, - "Deadband type '{0}' is not recognized.", + StatusCodes.BadDeadbandFilterInvalid, + "Deadband type '{0}' is not recognized.", DeadbandType); } // check data change trigger enumeration. - if ((int)Trigger < (int)DataChangeTrigger.Status || (int)Trigger > (int)DataChangeTrigger.StatusValueTimestamp) + if ((int)Trigger < (int)DataChangeTrigger.Status || (int)Trigger > (int)DataChangeTrigger.StatusValueTimestamp) { return ServiceResult.Create( - StatusCodes.BadDeadbandFilterInvalid, - "Deadband trigger '{0}' is not recognized.", + StatusCodes.BadDeadbandFilterInvalid, + "Deadband trigger '{0}' is not recognized.", Trigger); } @@ -50,8 +50,8 @@ public ServiceResult Validate() if (DeadbandValue < 0) { return ServiceResult.Create( - StatusCodes.BadDeadbandFilterInvalid, - "Deadband value '{0}' cannot be less than zero.", + StatusCodes.BadDeadbandFilterInvalid, + "Deadband value '{0}' cannot be less than zero.", DeadbandValue); } @@ -61,8 +61,8 @@ public ServiceResult Validate() if (DeadbandValue > 100) { return ServiceResult.Create( - StatusCodes.BadDeadbandFilterInvalid, - "Percentage deadband value '{0}' cannot be greater than 100.", + StatusCodes.BadDeadbandFilterInvalid, + "Percentage deadband value '{0}' cannot be greater than 100.", DeadbandValue); } } @@ -70,15 +70,13 @@ public ServiceResult Validate() // passed initial validation. return ServiceResult.Good; } - + /// /// Returns the AbsoluteDeadband if the filter specifies one. Zero otherwize. /// public static double GetAbsoluteDeadband(MonitoringFilter filter) { - DataChangeFilter datachangeFilter = filter as DataChangeFilter; - - if (datachangeFilter == null) + if (!(filter is DataChangeFilter datachangeFilter)) { return 0.0; } @@ -96,9 +94,7 @@ public static double GetAbsoluteDeadband(MonitoringFilter filter) /// public static double GetPercentageDeadband(MonitoringFilter filter) { - DataChangeFilter datachangeFilter = filter as DataChangeFilter; - - if (datachangeFilter == null) + if (!(filter is DataChangeFilter datachangeFilter)) { return 0.0; } @@ -112,7 +108,7 @@ public static double GetPercentageDeadband(MonitoringFilter filter) } #endregion } - + /// /// A filter to apply when monitoring event. /// @@ -126,7 +122,7 @@ public void AddSelectClause(NodeId eventTypeId, QualifiedName propertyName) SimpleAttributeOperand clause = new SimpleAttributeOperand(); clause.TypeDefinitionId = eventTypeId; - clause.AttributeId = Attributes.Value; + clause.AttributeId = Attributes.Value; clause.BrowsePath.Add(propertyName); @@ -141,12 +137,12 @@ public void AddSelectClause(NodeId eventTypeId, string browsePath, uint attribut SimpleAttributeOperand clause = new SimpleAttributeOperand(); clause.TypeDefinitionId = eventTypeId; - clause.AttributeId = attributeId; + clause.AttributeId = attributeId; if (!String.IsNullOrEmpty(browsePath)) { clause.BrowsePath = SimpleAttributeOperand.Parse(browsePath); - } + } SelectClauses.Add(clause); } @@ -173,13 +169,13 @@ public static implicit operator Result(ServiceResult status) result.Status = status; return result; } - + /// /// The result for the entire filter. /// public ServiceResult Status { - get { return m_status; } + get { return m_status; } set { m_status = value; } } @@ -200,7 +196,7 @@ public string GetLongString() } if (ServiceResult.IsBad(WhereClauseResult.Status)) - { + { buffer.AppendFormat("Where Clause Error: {0}", WhereClauseResult.Status.ToString()); buffer.AppendLine(); @@ -215,7 +211,7 @@ public string GetLongString() { if (ServiceResult.IsBad(operandResult)) { - buffer.AppendFormat("Operand Error: {0}",operandResult.ToString()); + buffer.AppendFormat("Operand Error: {0}", operandResult.ToString()); buffer.AppendLine(); } } @@ -225,7 +221,7 @@ public string GetLongString() return buffer.ToString(); } - + /// /// The result for each select clause. /// @@ -252,12 +248,12 @@ public Opc.Ua.ContentFilter.Result WhereClauseResult return m_whereClauseResults; } - internal set + internal set { m_whereClauseResults = value; } } - + /// /// Converts the object to an EventFilterResult. /// @@ -290,7 +286,7 @@ public EventFilterResult ToEventFilterResult(DiagnosticsMasks diagnosticsMasks, return result; } #endregion - + #region Private Fields private ServiceResult m_status; private List m_selectClauseResults; @@ -309,16 +305,16 @@ public Result Validate(FilterContext context) if (m_selectClauses == null || m_selectClauses.Count == 0) { result.Status = ServiceResult.Create( - StatusCodes.BadStructureMissing, + StatusCodes.BadStructureMissing, "EventFilter does not specify any Select Clauses."); return result; - } + } if (m_whereClause == null) { result.Status = ServiceResult.Create( - StatusCodes.BadStructureMissing, + StatusCodes.BadStructureMissing, "EventFilter does not specify any Where Clauses."); return result; @@ -337,7 +333,7 @@ public Result Validate(FilterContext context) if (clause == null) { clauseResult = ServiceResult.Create( - StatusCodes.BadStructureMissing, + StatusCodes.BadStructureMissing, "EventFilterSelectClause cannot be null in EventFilter SelectClause."); result.SelectClauseResults.Add(clauseResult); @@ -354,7 +350,7 @@ public Result Validate(FilterContext context) error = true; continue; } - + // clause ok. result.SelectClauseResults.Add(null); } @@ -367,7 +363,7 @@ public Result Validate(FilterContext context) { result.SelectClauseResults.Clear(); } - + // validate where clause. result.WhereClauseResult = m_whereClause.Validate(context); @@ -379,7 +375,7 @@ public Result Validate(FilterContext context) return result; } } - + /// /// A clause that identifies a field to return with the event. /// @@ -394,9 +390,9 @@ public SimpleAttributeOperand( QualifiedName browsePath) { m_typeDefinitionId = typeId; - m_browsePath = new QualifiedNameCollection(); - m_attributeId = Attributes.Value; - m_indexRange = null; + m_browsePath = new QualifiedNameCollection(); + m_attributeId = Attributes.Value; + m_indexRange = null; m_browsePath.Add(browsePath); } @@ -409,42 +405,42 @@ public SimpleAttributeOperand( IList browsePath) { m_typeDefinitionId = typeId; - m_browsePath = new QualifiedNameCollection(browsePath); - m_attributeId = Attributes.Value; - m_indexRange = null; + m_browsePath = new QualifiedNameCollection(browsePath); + m_attributeId = Attributes.Value; + m_indexRange = null; } /// /// Creates an operand that references a component/property of a type. /// public SimpleAttributeOperand( - FilterContext context, - ExpandedNodeId typeId, + FilterContext context, + ExpandedNodeId typeId, IList browsePath) { m_typeDefinitionId = ExpandedNodeId.ToNodeId(typeId, context.NamespaceUris); - m_browsePath = new QualifiedNameCollection(browsePath); - m_attributeId = Attributes.Value; - m_indexRange = null; + m_browsePath = new QualifiedNameCollection(browsePath); + m_attributeId = Attributes.Value; + m_indexRange = null; } - + /// /// Creates an operand that references a component/property of a type. /// public SimpleAttributeOperand( - FilterContext context, + FilterContext context, ExpandedNodeId typeDefinitionId, - string browsePath, - uint attributeId, - string indexRange) + string browsePath, + uint attributeId, + string indexRange) { m_typeDefinitionId = ExpandedNodeId.ToNodeId(typeDefinitionId, context.NamespaceUris); - m_browsePath = Parse(browsePath); - m_attributeId = attributeId; - m_indexRange = indexRange; + m_browsePath = Parse(browsePath); + m_attributeId = attributeId; + m_indexRange = indexRange; } #endregion - + #region IFormattable Members /// /// Formats the value of the current instance using the specified format. @@ -475,7 +471,7 @@ public string ToString(string format, IFormatProvider formatProvider) throw new FormatException(Utils.Format("Invalid format string: '{0}'.", format)); } #endregion - + #region Overridden Methods /// /// Returns a that represents the current . @@ -525,8 +521,8 @@ public override ServiceResult Validate(FilterContext context, int index) if (!Attributes.IsValid(m_attributeId)) { return ServiceResult.Create( - StatusCodes.BadAttributeIdInvalid, - "SimpleAttributeOperand does not specify a valid AttributeId ({0}).", + StatusCodes.BadAttributeIdInvalid, + "SimpleAttributeOperand does not specify a valid AttributeId ({0}).", m_attributeId); } @@ -544,16 +540,16 @@ public override ServiceResult Validate(FilterContext context, int index) { return ServiceResult.Create( e, - StatusCodes.BadIndexRangeInvalid, - "SimpleAttributeOperand does not specify a valid BrowsePath ({0}).", + StatusCodes.BadIndexRangeInvalid, + "SimpleAttributeOperand does not specify a valid BrowsePath ({0}).", m_indexRange); } if (m_attributeId != Attributes.Value) { return ServiceResult.Create( - StatusCodes.BadIndexRangeInvalid, - "SimpleAttributeOperand specifies an IndexRange for an Attribute other than Value ({0}).", + StatusCodes.BadIndexRangeInvalid, + "SimpleAttributeOperand specifies an IndexRange for an Attribute other than Value ({0}).", m_attributeId); } } @@ -580,7 +576,7 @@ public override string ToString(INodeTable nodeTable) { buffer.AppendFormat("{0}", TypeDefinitionId); } - + if (BrowsePath != null && BrowsePath.Count > 0) { buffer.AppendFormat("{0}", Format(BrowsePath)); @@ -590,7 +586,7 @@ public override string ToString(INodeTable nodeTable) { buffer.AppendFormat("[{0}]", NumericRange.Parse(IndexRange)); } - + return buffer.ToString(); } #endregion @@ -618,7 +614,7 @@ public static string Format(IList browsePath) } buffer.Append('/'); - + if (browseName.NamespaceIndex != 0) { buffer.AppendFormat("{0}:", browseName.NamespaceIndex); @@ -639,7 +635,7 @@ public static string Format(IList browsePath) return buffer.ToString(); } - + /// /// Parses a browse path. /// @@ -651,7 +647,7 @@ public static QualifiedNameCollection Parse(string browsePath) { return browseNames; } - + StringBuilder buffer = new StringBuilder(); bool escaped = false; @@ -697,7 +693,7 @@ public static QualifiedNameCollection Parse(string browsePath) return browseNames; } #endregion - + #region Private Fields private bool m_validated; private NumericRange m_parsedIndexRange; diff --git a/Stack/Opc.Ua.Core/Stack/Types/NotificationMessage.cs b/Stack/Opc.Ua.Core/Stack/Types/NotificationMessage.cs index fcc71ac43..0a04460c9 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/NotificationMessage.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/NotificationMessage.cs @@ -28,7 +28,7 @@ public partial class NotificationMessage /// public List StringTable { - get { return m_stringTable; } + get { return m_stringTable; } set { m_stringTable = value; } } @@ -69,16 +69,15 @@ public IList GetDataChanges(bool reverse) continue; } - DataChangeNotification notification = extension.Body as DataChangeNotification; - - if (notification == null) + + if (!(extension.Body is DataChangeNotification notification)) { continue; } - + if (reverse) { - for (int ii = notification.MonitoredItems.Count-1; ii >= 0; ii--) + for (int ii = notification.MonitoredItems.Count - 1; ii >= 0; ii--) { MonitoredItemNotification datachange = notification.MonitoredItems[ii]; @@ -106,7 +105,7 @@ public IList GetDataChanges(bool reverse) return datachanges; } - + /// /// Returns the events contained in the notification message. /// @@ -121,16 +120,15 @@ public IList GetEvents(bool reverse) continue; } - EventNotificationList notification = extension.Body as EventNotificationList; - - if (notification == null) + + if (!(extension.Body is EventNotificationList notification)) { continue; - } - + } + if (reverse) { - for (int ii = notification.Events.Count-1; ii >= 0; ii--) + for (int ii = notification.Events.Count - 1; ii >= 0; ii--) { EventFieldList eventFields = notification.Events[ii]; diff --git a/Stack/Opc.Ua.Core/Stack/Types/QueryDataSet.cs b/Stack/Opc.Ua.Core/Stack/Types/QueryDataSet.cs index d324299d2..4bb793270 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/QueryDataSet.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/QueryDataSet.cs @@ -25,13 +25,13 @@ public partial class NodeTypeDescription /// A handle assigned to the item during processing. /// public object Handle { get; set; } - + /// /// Whether the value has been processed. /// public bool Processed { get; set; } #endregion - + #region Private Fields #endregion } diff --git a/Stack/Opc.Ua.Core/Stack/Types/Range.cs b/Stack/Opc.Ua.Core/Stack/Types/Range.cs index b4f97a0fd..e9d24a8fc 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/Range.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/Range.cs @@ -27,14 +27,14 @@ public partial class Range /// public Range(double high, double low) { - m_low = low; + m_low = low; m_high = high; // swap values if high is not actually higher. if (low > high) { m_high = low; - m_low = high; + m_low = high; } } diff --git a/Stack/Opc.Ua.Core/Stack/Types/ReadValueId.cs b/Stack/Opc.Ua.Core/Stack/Types/ReadValueId.cs index e3d8d785f..58897a85e 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/ReadValueId.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/ReadValueId.cs @@ -18,9 +18,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. namespace Opc.Ua { #region ReadValueId Class - /// - /// The description of a value to read. - /// + /// + /// The description of a value to read. + /// public partial class ReadValueId { #region Supporting Properties and Methods @@ -29,16 +29,16 @@ public partial class ReadValueId /// public object Handle { - get { return m_handle; } + get { return m_handle; } set { m_handle = value; } } - + /// /// Whether the value has been processed. /// public bool Processed { - get { return m_processed; } + get { return m_processed; } set { m_processed = value; } } @@ -47,10 +47,10 @@ public bool Processed /// public NumericRange ParsedIndexRange { - get { return m_parsedIndexRange; } + get { return m_parsedIndexRange; } set { m_parsedIndexRange = value; } } - + /// /// Validates a read value id parameter. /// @@ -67,13 +67,13 @@ public static ServiceResult Validate(ReadValueId valueId) { return StatusCodes.BadNodeIdInvalid; } - + // must be a legimate attribute value. if (!Attributes.IsValid(valueId.AttributeId)) { return StatusCodes.BadAttributeIdInvalid; } - + // data encoding and index range is only valid for value attributes. if (valueId.AttributeId != Attributes.Value) { @@ -112,7 +112,7 @@ public static ServiceResult Validate(ReadValueId valueId) return null; } #endregion - + #region Private Fields private object m_handle; private bool m_processed; diff --git a/Stack/Opc.Ua.Core/Stack/Types/ReferenceDescription.cs b/Stack/Opc.Ua.Core/Stack/Types/ReferenceDescription.cs index 79998499d..83f1639d4 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/ReferenceDescription.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/ReferenceDescription.cs @@ -18,11 +18,11 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. namespace Opc.Ua { #region ReferenceDescription Class - /// - /// A reference returned in browse operation. - /// + /// + /// A reference returned in browse operation. + /// public partial class ReferenceDescription : IFormattable - { + { #region IFormattable Members /// /// Returns the string representation of the object. @@ -43,10 +43,10 @@ public string ToString(string format, IFormatProvider formatProvider) return Utils.Format("(unknown {0})", ((NodeClass)m_nodeClass).ToString().ToLower()); } - + throw new FormatException(Utils.Format("Invalid format string: '{0}'.", format)); } - + /// /// Returns the string representation of the object. /// @@ -62,8 +62,8 @@ public override string ToString() /// public void SetReferenceType( BrowseResultMask resultMask, - NodeId referenceTypeId, - bool isForward) + NodeId referenceTypeId, + bool isForward) { if ((resultMask & BrowseResultMask.ReferenceTypeId) != 0) { @@ -89,10 +89,10 @@ public void SetReferenceType( /// public void SetTargetAttributes( BrowseResultMask resultMask, - NodeClass nodeClass, - QualifiedName browseName, - LocalizedText displayName, - ExpandedNodeId typeDefinition) + NodeClass nodeClass, + QualifiedName browseName, + LocalizedText displayName, + ExpandedNodeId typeDefinition) { if ((resultMask & BrowseResultMask.NodeClass) != 0) { @@ -138,7 +138,7 @@ public void SetTargetAttributes( /// public bool Unfiltered { - get { return m_unfiltered; } + get { return m_unfiltered; } set { m_unfiltered = value; } } #endregion diff --git a/Stack/Opc.Ua.Core/Stack/Types/ServiceFault.cs b/Stack/Opc.Ua.Core/Stack/Types/ServiceFault.cs index 253f6ac63..506d5b6bb 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/ServiceFault.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/ServiceFault.cs @@ -17,12 +17,12 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. namespace Opc.Ua { - #region ServiceFault Class - /// - /// The ServiceFault class. - /// - public partial class ServiceFault : IServiceResponse - { - } - #endregion + #region ServiceFault Class + /// + /// The ServiceFault class. + /// + public partial class ServiceFault : IServiceResponse + { + } + #endregion } diff --git a/Stack/Opc.Ua.Core/Stack/Types/StatusResult.cs b/Stack/Opc.Ua.Core/Stack/Types/StatusResult.cs index 1ad50c3d8..d9c16c4a2 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/StatusResult.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/StatusResult.cs @@ -45,12 +45,12 @@ public void ApplyDiagnosticMasks(DiagnosticsMasks diagnosticMasks, StringTable s { if (m_result != null) { - m_statusCode = m_result.StatusCode; + m_statusCode = m_result.StatusCode; m_diagnosticInfo = new DiagnosticInfo(m_result, diagnosticMasks, false, stringTable); } } #endregion - + #region Private Fields private ServiceResult m_result; #endregion diff --git a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs index cb2409e30..e2aeb4d70 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs @@ -20,7 +20,7 @@ namespace Opc.Ua /// The UserIdentityToken class. /// public partial class UserIdentityToken - { + { #region Public Methods /// /// Encrypts the token (implemented by the subclass). @@ -28,14 +28,14 @@ public partial class UserIdentityToken public virtual void Encrypt(X509Certificate2 certificate, byte[] receiverNonce, string securityPolicyUri) { } - + /// /// Decrypts the token (implemented by the subclass). /// public virtual void Decrypt(X509Certificate2 certificate, byte[] receiverNonce, string securityPolicyUri) { } - + /// /// Creates a signature with the token (implemented by the subclass). /// @@ -43,7 +43,7 @@ public virtual SignatureData Sign(byte[] dataToSign, string securityPolicyUri) { return new SignatureData(); } - + /// /// Verifies a signature created with the token (implemented by the subclass). /// @@ -65,11 +65,11 @@ public partial class UserNameIdentityToken /// public string DecryptedPassword { - get { return m_decryptedPassword; } + get { return m_decryptedPassword; } set { m_decryptedPassword = value; } } #endregion - + #region Public Methods /// /// Encrypts the DecryptedPassword using the EncryptionAlgorithm and places the result in Password @@ -85,13 +85,13 @@ public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, s // handle no encryption. if (String.IsNullOrEmpty(securityPolicyUri) || securityPolicyUri == SecurityPolicies.None) { - m_password = new UTF8Encoding().GetBytes(m_decryptedPassword); + m_password = Encoding.UTF8.GetBytes(m_decryptedPassword); m_encryptionAlgorithm = null; return; } - + // encrypt the password. - byte[] dataToEncrypt = Utils.Append(new UTF8Encoding().GetBytes(m_decryptedPassword), senderNonce); + byte[] dataToEncrypt = Utils.Append(Encoding.UTF8.GetBytes(m_decryptedPassword), senderNonce); EncryptedData encryptedData = SecurityPolicies.Encrypt( certificate, @@ -99,7 +99,7 @@ public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, s dataToEncrypt); m_password = encryptedData.Data; - m_encryptionAlgorithm = encryptedData.Algorithm; + m_encryptionAlgorithm = encryptedData.Algorithm; } /// @@ -110,7 +110,7 @@ public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, s // handle no encryption. if (String.IsNullOrEmpty(securityPolicyUri) || securityPolicyUri == SecurityPolicies.None) { - m_decryptedPassword = new UTF8Encoding().GetString(m_password, 0, m_password.Length); + m_decryptedPassword = Encoding.UTF8.GetString(m_password, 0, m_password.Length); return; } @@ -120,8 +120,8 @@ public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, s encryptedData.Algorithm = m_encryptionAlgorithm; byte[] decryptedPassword = SecurityPolicies.Decrypt( - certificate, - securityPolicyUri, + certificate, + securityPolicyUri, encryptedData); if (decryptedPassword == null) @@ -150,7 +150,7 @@ public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, s } // convert to UTF-8. - m_decryptedPassword = new UTF8Encoding().GetString(decryptedPassword, 0, startOfNonce); + m_decryptedPassword = Encoding.UTF8.GetString(decryptedPassword, 0, startOfNonce); } #endregion @@ -189,22 +189,22 @@ public X509Certificate2 Certificate public override SignatureData Sign(byte[] dataToSign, string securityPolicyUri) { X509Certificate2 certificate = m_certificate; - + if (certificate == null) - { + { certificate = CertificateFactory.Create(m_certificateData, true); } - + SignatureData signatureData = SecurityPolicies.Sign( - certificate, - securityPolicyUri, + certificate, + securityPolicyUri, dataToSign); - + m_certificateData = certificate.RawData; return signatureData; } - + /// /// Verifies a signature created with the token. /// @@ -268,7 +268,7 @@ public enum IssuedTokenType /// The IssuedIdentityToken class. /// public partial class IssuedIdentityToken - { + { #region Public Properties /// /// The type of issued token. @@ -284,11 +284,11 @@ public IssuedTokenType IssuedTokenType /// public byte[] DecryptedTokenData { - get { return m_decryptedTokenData; } + get { return m_decryptedTokenData; } set { m_decryptedTokenData = value; } } #endregion - + #region Public Methods /// /// Encrypts the DecryptedTokenData using the EncryptionAlgorithm and places the result in Password @@ -309,11 +309,11 @@ public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, s certificate, securityPolicyUri, dataToEncrypt); - + m_tokenData = encryptedData.Data; m_encryptionAlgorithm = encryptedData.Algorithm; } - + /// /// Decrypts the Password using the EncryptionAlgorithm and places the result in DecryptedPassword /// @@ -332,8 +332,8 @@ public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, s encryptedData.Algorithm = m_encryptionAlgorithm; byte[] decryptedTokenData = SecurityPolicies.Decrypt( - certificate, - securityPolicyUri, + certificate, + securityPolicyUri, encryptedData); // verify the sender's nonce. @@ -345,16 +345,16 @@ public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, s for (int ii = 0; ii < senderNonce.Length; ii++) { - if (senderNonce[ii] != decryptedTokenData[ii+startOfNonce]) + if (senderNonce[ii] != decryptedTokenData[ii + startOfNonce]) { throw new ServiceResultException(StatusCodes.BadIdentityTokenRejected); } } - } - + } + // copy results. m_decryptedTokenData = new byte[startOfNonce]; - Array.Copy(decryptedTokenData, m_decryptedTokenData, startOfNonce); + Array.Copy(decryptedTokenData, m_decryptedTokenData, startOfNonce); } /// @@ -364,7 +364,7 @@ public override SignatureData Sign(byte[] dataToSign, string securityPolicyUri) { return null; } - + /// /// Verifies a signature created with the token. /// diff --git a/Stack/Opc.Ua.Core/Stack/Types/VariableAttributes.cs b/Stack/Opc.Ua.Core/Stack/Types/VariableAttributes.cs index f093e8b23..f42019788 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/VariableAttributes.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/VariableAttributes.cs @@ -28,21 +28,21 @@ public partial class VariableAttributes public VariableAttributes(object value, byte accessLevel) { Initialize(); - - Value = new Variant(value); - AccessLevel = accessLevel; - UserAccessLevel = accessLevel; + + Value = new Variant(value); + AccessLevel = accessLevel; + UserAccessLevel = accessLevel; MinimumSamplingInterval = MinimumSamplingIntervals.Indeterminate; - Historizing = false; + Historizing = false; if (value == null) { - DataType = DataTypes.BaseDataType; + DataType = DataTypes.BaseDataType; ValueRank = ValueRanks.Any; } else { - DataType = TypeInfo.GetDataTypeId(value); + DataType = TypeInfo.GetDataTypeId(value); ValueRank = TypeInfo.GetValueRank(value); } } diff --git a/Stack/Opc.Ua.Core/Stack/Types/WriteValue.cs b/Stack/Opc.Ua.Core/Stack/Types/WriteValue.cs index 186a33a5c..85b507915 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/WriteValue.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/WriteValue.cs @@ -17,9 +17,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. namespace Opc.Ua { - /// - /// The description of a value to write. - /// + /// + /// The description of a value to write. + /// public partial class WriteValue { #region Supporting Properties and Methods @@ -28,7 +28,7 @@ public partial class WriteValue /// public object Handle { - get { return m_handle; } + get { return m_handle; } set { m_handle = value; } } @@ -37,7 +37,7 @@ public object Handle /// public bool Processed { - get { return m_processed; } + get { return m_processed; } set { m_processed = value; } } @@ -46,10 +46,10 @@ public bool Processed /// public NumericRange ParsedIndexRange { - get { return m_parsedIndexRange; } + get { return m_parsedIndexRange; } set { m_parsedIndexRange = value; } } - + /// /// Validates a write value parameter. /// @@ -66,7 +66,7 @@ public static ServiceResult Validate(WriteValue value) { return StatusCodes.BadNodeIdInvalid; } - + // must be a legimate attribute value. if (!Attributes.IsValid(value.AttributeId)) { @@ -88,11 +88,9 @@ public static ServiceResult Validate(WriteValue value) return ServiceResult.Create(e, StatusCodes.BadIndexRangeInvalid, String.Empty); } - if(value.ParsedIndexRange.SubRanges != null) + if (value.ParsedIndexRange.SubRanges != null) { - Matrix matrix = value.Value.Value as Matrix; - - if (matrix == null) + if (!(value.Value.Value is Matrix matrix)) { // Check for String or ByteString arrays. Those DataTypes have special handling // when using sub ranges. @@ -108,11 +106,7 @@ public static ServiceResult Validate(WriteValue value) } else { - // check that value provided is actually an array. - Array array = value.Value.Value as Array; - string str = value.Value.Value as string; - - if (array != null) + if (value.Value.Value is Array array) { NumericRange range = value.ParsedIndexRange; @@ -128,7 +122,7 @@ public static ServiceResult Validate(WriteValue value) return StatusCodes.BadIndexRangeInvalid; } } - else if(str != null) + else if (value.Value.Value is string str) { NumericRange range = value.ParsedIndexRange; @@ -149,7 +143,7 @@ public static ServiceResult Validate(WriteValue value) return StatusCodes.BadTypeMismatch; } } - + } else { @@ -158,9 +152,9 @@ public static ServiceResult Validate(WriteValue value) // passed basic validation. return null; - } + } #endregion - + #region Private Fields private object m_handle; private bool m_processed; diff --git a/Stack/Opc.Ua.Core/Types/BuiltIn/DataValue.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/DataValue.cs index b3e894fb6..52b3221a4 100644 --- a/Stack/Opc.Ua.Core/Types/BuiltIn/DataValue.cs +++ b/Stack/Opc.Ua.Core/Types/BuiltIn/DataValue.cs @@ -214,9 +214,8 @@ public override bool Equals(object obj) return true; } - DataValue value = obj as DataValue; - if (value != null) + if (obj is DataValue value) { if (this.m_statusCode != value.m_statusCode) { @@ -245,7 +244,7 @@ public override bool Equals(object obj) return Utils.IsEqual(this.m_value.Value, value.m_value.Value); } - + return false; } @@ -575,9 +574,8 @@ public object GetValue(Type expectedType) return null; } - ExtensionObject extension = value as ExtensionObject; - if (extension != null) + if (value is ExtensionObject extension) { value = extension.Body; } @@ -614,9 +612,8 @@ public T GetValue(T defaultValue) return (T)this.Value; } - ExtensionObject extension = this.Value as ExtensionObject; - if (extension != null) + if (this.Value is ExtensionObject extension) { if (typeof(T).IsInstanceOfType(extension.Body)) { diff --git a/Stack/Opc.Ua.Core/Types/BuiltIn/DiagnosticInfo.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/DiagnosticInfo.cs index 114ad27d9..977274065 100644 --- a/Stack/Opc.Ua.Core/Types/BuiltIn/DiagnosticInfo.cs +++ b/Stack/Opc.Ua.Core/Types/BuiltIn/DiagnosticInfo.cs @@ -506,9 +506,8 @@ private bool Equals(object obj, int depth) return true; } - DiagnosticInfo value = obj as DiagnosticInfo; - if (value != null) + if (obj is DiagnosticInfo value) { if (this.m_symbolicId != value.m_symbolicId) diff --git a/Stack/Opc.Ua.Core/Types/BuiltIn/ExtensionObject.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/ExtensionObject.cs index 108582233..74a5de182 100644 --- a/Stack/Opc.Ua.Core/Types/BuiltIn/ExtensionObject.cs +++ b/Stack/Opc.Ua.Core/Types/BuiltIn/ExtensionObject.cs @@ -447,9 +447,8 @@ public override bool Equals(object obj) return true; } - ExtensionObject value = obj as ExtensionObject; - if (value != null) + if (obj is ExtensionObject value) { if (this.m_typeId != value.m_typeId) { @@ -509,19 +508,19 @@ public string ToString(string format, IFormatProvider formatProvider) { if (format == null) { - if (m_body is byte[]) + if (m_body is byte[] byteString) { - return String.Format(formatProvider, "Byte[{0}]", ((byte[])m_body).Length); + return String.Format(formatProvider, "Byte[{0}]", byteString.Length); } - if (m_body is XmlElement) + if (m_body is XmlElement element) { - return String.Format(formatProvider, "<{0}>", ((XmlElement)m_body).Name); + return String.Format(formatProvider, "<{0}>", element.Name); } - if (m_body is IFormattable) + if (m_body is IFormattable formattable) { - return String.Format(formatProvider, "{0}", ((IFormattable)m_body).ToString(null, formatProvider)); + return String.Format(formatProvider, "{0}", formattable.ToString(null, formatProvider)); } if (m_body is IEncodeable) @@ -536,9 +535,7 @@ public string ToString(string format, IFormatProvider formatProvider) for (int ii = 0; ii < attributes.Length; ii++) { - DataMemberAttribute contract = attributes[ii] as DataMemberAttribute; - - if (contract != null) + if (attributes[ii] is DataMemberAttribute contract) { if (body.Length == 0) { @@ -643,9 +640,7 @@ public static IEncodeable ToEncodeable(ExtensionObject extension) /// public static Array ToArray(object source, Type elementType) { - var extensions = source as Array; - - if (extensions == null) + if (!(source is Array extensions)) { return null; } @@ -675,9 +670,7 @@ public static Array ToArray(object source, Type elementType) /// public static List ToList(object source) where T : class { - var extensions = source as Array; - - if (extensions == null) + if (!(source is Array extensions)) { return null; } @@ -715,9 +708,8 @@ private NodeId XmlEncodedTypeId get { // must use the XML encoding id if encoding in an XML stream. - IEncodeable encodeable = m_body as IEncodeable; - if (encodeable != null) + if (m_body is IEncodeable encodeable) { return ExpandedNodeId.ToNodeId(encodeable.XmlEncodingId, m_context.NamespaceUris); } @@ -779,9 +771,8 @@ private XmlElement XmlEncodedBody Body = decoder.ReadExtensionObjectBody(m_typeId); // clear the type id for encodeables. - IEncodeable encodeable = m_body as IEncodeable; - if (encodeable != null) + if (m_body is IEncodeable encodeable) { m_typeId = ExpandedNodeId.Null; } @@ -925,9 +916,8 @@ public static ExtensionObjectCollection ToExtensionObjects(IEnumerable - /// Stores the type tree for a server. + /// Stores the type tree for a server. /// public interface ITypeTable { @@ -51,6 +53,24 @@ public interface ITypeTable /// The immediate supertype idnetyfier for NodeId FindSuperType(NodeId typeId); +#if (NET_STANDARD_ASYNC) + /// + /// Returns the immediate supertype for the type. + /// + /// The extended type identifier. + /// + /// A type identifier of the + Task FindSuperTypeAsync(ExpandedNodeId typeId, CancellationToken ct = default); + + /// + /// Returns the immediate supertype for the type. + /// + /// The type identifier. + /// + /// The immediate supertype idnetyfier for + Task FindSuperTypeAsync(NodeId typeId, CancellationToken ct = default); +#endif + /// /// Returns the immediate subtypes for the type. /// @@ -93,7 +113,7 @@ public interface ITypeTable NodeId FindReferenceType(QualifiedName browseName); /// - /// Checks if the identifier represents a that provides encodings + /// Checks if the identifier represents a that provides encodings /// for the . /// /// The id the encoding node . @@ -109,7 +129,7 @@ public interface ITypeTable /// The identifier of the expected type . /// The value. /// - /// true if the value contained in an extension object matches the + /// true if the value contained in an extension object matches the /// expected data type; otherwise, false. /// bool IsEncodingFor(NodeId expectedTypeId, ExtensionObject value); diff --git a/Stack/Opc.Ua.Core/Types/BuiltIn/Matrix.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/Matrix.cs index e8f49449f..d05824dac 100644 --- a/Stack/Opc.Ua.Core/Types/BuiltIn/Matrix.cs +++ b/Stack/Opc.Ua.Core/Types/BuiltIn/Matrix.cs @@ -135,9 +135,8 @@ public override bool Equals(object obj) return true; } - Matrix matrix = obj as Matrix; - if (matrix != null) + if (obj is Matrix matrix) { if (!m_typeInfo.Equals(matrix.TypeInfo)) { diff --git a/Stack/Opc.Ua.Core/Types/BuiltIn/NodeId.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/NodeId.cs index f58f5b50c..31a7a3a42 100644 --- a/Stack/Opc.Ua.Core/Types/BuiltIn/NodeId.cs +++ b/Stack/Opc.Ua.Core/Types/BuiltIn/NodeId.cs @@ -1118,7 +1118,7 @@ public int CompareTo(object obj) return CompareTo(idType, id); } -#endregion + #endregion #region public static bool operator>(NodeId value1, NodeId value2) /// @@ -1156,7 +1156,7 @@ public int CompareTo(object obj) } #endregion -#endregion + #endregion #region IFormattable Members @@ -1543,9 +1543,7 @@ private static int CompareIdentifiers(IdType idType1, object id1, IdType idType2 case IdType.String: { - string text = nonNull as string; - - if (text != null && text.Length == 0) + if (nonNull is string text && text.Length == 0) { return 0; } @@ -1555,9 +1553,7 @@ private static int CompareIdentifiers(IdType idType1, object id1, IdType idType2 case IdType.Opaque: { - byte[] bytes = nonNull as byte[]; - - if (bytes != null && bytes.Length == 0) + if (nonNull is byte[] bytes && bytes.Length == 0) { return 0; } @@ -1569,13 +1565,10 @@ private static int CompareIdentifiers(IdType idType1, object id1, IdType idType2 return (id1 == null) ? -1 : +1; } - byte[] bytes1 = id1 as byte[]; - if (bytes1 != null) + if (id1 is byte[] bytes1) { - byte[] bytes2 = id2 as byte[]; - - if (bytes2 == null) + if (!(id2 is byte[] bytes2)) { return +1; } @@ -1599,9 +1592,8 @@ private static int CompareIdentifiers(IdType idType1, object id1, IdType idType2 return 0; } - IComparable comparable1 = id1 as IComparable; - if (comparable1 != null) + if (id1 is IComparable comparable1) { return comparable1.CompareTo(id2); } diff --git a/Stack/Opc.Ua.Core/Types/BuiltIn/NodeIdDictionary.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/NodeIdDictionary.cs index 7d507cde4..9ff302c3b 100644 --- a/Stack/Opc.Ua.Core/Types/BuiltIn/NodeIdDictionary.cs +++ b/Stack/Opc.Ua.Core/Types/BuiltIn/NodeIdDictionary.cs @@ -252,7 +252,7 @@ public bool Remove(NodeId key) /// public bool TryGetValue(NodeId key, out T value) { - value = default(T); + value = default; if (key == null) { @@ -1029,9 +1029,7 @@ private void ReleaseEnumerator() { if (m_enumerator != null) { - IDisposable diposeable = m_enumerator as IDisposable; - - if (diposeable != null) + if (m_enumerator is IDisposable diposeable) { diposeable.Dispose(); } diff --git a/Stack/Opc.Ua.Core/Types/BuiltIn/SessionLessServiceMessage.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/SessionLessServiceMessage.cs index 23ad9e435..eee634022 100644 --- a/Stack/Opc.Ua.Core/Types/BuiltIn/SessionLessServiceMessage.cs +++ b/Stack/Opc.Ua.Core/Types/BuiltIn/SessionLessServiceMessage.cs @@ -17,7 +17,7 @@ namespace Opc.Ua /// /// A session-less service message. /// - public class SessionLessServiceMessage + public class SessionLessServiceMessage { /// /// The VersionTime of the namespaces URIs on the server. diff --git a/Stack/Opc.Ua.Core/Types/BuiltIn/StatusCode.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/StatusCode.cs index e45288947..e38052def 100644 --- a/Stack/Opc.Ua.Core/Types/BuiltIn/StatusCode.cs +++ b/Stack/Opc.Ua.Core/Types/BuiltIn/StatusCode.cs @@ -119,9 +119,7 @@ public StatusCode(uint code) /// The exception to convert to a status code public StatusCode(Exception e, uint defaultCode) { - ServiceResultException sre = e as ServiceResultException; - - if (sre != null) + if (e is ServiceResultException sre) { m_code = sre.StatusCode; } diff --git a/Stack/Opc.Ua.Core/Types/BuiltIn/Uuid.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/Uuid.cs index 8e8965074..5089824ca 100644 --- a/Stack/Opc.Ua.Core/Types/BuiltIn/Uuid.cs +++ b/Stack/Opc.Ua.Core/Types/BuiltIn/Uuid.cs @@ -24,7 +24,7 @@ namespace Opc.Ua /// and encoded/decoded to/from an underlying stream. /// x [DataContract(Name = "Guid", Namespace = Namespaces.OpcUaXsd)] - public struct Uuid : IComparable, IFormattable + public struct Uuid : IComparable, IFormattable, IEquatable { #region Constructors /// @@ -207,6 +207,18 @@ public override bool Equals(object obj) return (CompareTo(obj) == 0); } + /// + /// Returns true if the objects are equal. + /// + /// + /// Returns true if the objects are equal. + /// + /// The object being compared to *this* object + public bool Equals(Uuid other) + { + return (CompareTo(other) == 0); + } + /// /// Returns a hash code for the object. /// @@ -247,7 +259,7 @@ public int CompareTo(object obj) return ((Uuid)obj).m_guid.CompareTo(m_guid); } - // compare guids. + // compare guids. if (obj is Guid) { return m_guid.CompareTo((Guid)obj); @@ -274,7 +286,7 @@ public string ToString(string format, IFormatProvider formatProvider) #region Private Fields private Guid m_guid; - #endregion + #endregion } #region UuidCollection Class diff --git a/Stack/Opc.Ua.Core/Types/BuiltIn/Variant.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/Variant.cs index daf5f269d..bd44004c5 100644 --- a/Stack/Opc.Ua.Core/Types/BuiltIn/Variant.cs +++ b/Stack/Opc.Ua.Core/Types/BuiltIn/Variant.cs @@ -910,9 +910,8 @@ private void AppendFormat(StringBuilder buffer, object value, IFormatProvider fo } // recusrively write individual elements of an array. - Array array = value as Array; - if (array != null && m_typeInfo.ValueRank <= 1) + if (value is Array array && m_typeInfo.ValueRank <= 1) { buffer.Append('{'); @@ -2288,9 +2287,8 @@ private void SetScalar(object value, TypeInfo typeInfo) } // check for matrix - Matrix matrix = value as Matrix; - if (matrix != null) + if (value is Matrix matrix) { m_value = matrix; return; @@ -2320,9 +2318,7 @@ private void SetScalar(object value, TypeInfo typeInfo) // convert encodeables to extension objects. case BuiltInType.ExtensionObject: { - IEncodeable encodeable = value as IEncodeable; - - if (encodeable != null) + if (value is IEncodeable encodeable) { m_value = new ExtensionObject(encodeable); return; @@ -2384,9 +2380,7 @@ private void SetArray(Array array, TypeInfo typeInfo) // convert Guids to Uuids. case BuiltInType.Guid: { - Guid[] guids = array as Guid[]; - - if (guids != null) + if (array is Guid[] guids) { Set(guids); return; @@ -2399,9 +2393,7 @@ private void SetArray(Array array, TypeInfo typeInfo) // convert encodeables to extension objects. case BuiltInType.ExtensionObject: { - IEncodeable[] encodeables = array as IEncodeable[]; - - if (encodeables != null) + if (array is IEncodeable[] encodeables) { ExtensionObject[] extensions = new ExtensionObject[encodeables.Length]; @@ -2421,9 +2413,7 @@ private void SetArray(Array array, TypeInfo typeInfo) // convert objects to variants objects. case BuiltInType.Variant: { - object[] objects = array as object[]; - - if (objects != null) + if (array is object[] objects) { Variant[] variants = new Variant[objects.Length]; @@ -2462,9 +2452,7 @@ private void SetList(IList value, TypeInfo typeInfo) { if (typeInfo.BuiltInType == BuiltInType.ExtensionObject) { - IEncodeable encodeable = value[ii] as IEncodeable; - - if (encodeable != null) + if (value[ii] is IEncodeable encodeable) { array.SetValue(new ExtensionObject(encodeable), ii); continue; @@ -2510,9 +2498,8 @@ private void Set(object value, TypeInfo typeInfo) } // handle lists. - IList list = value as IList; - if (list != null) + if (value is IList list) { SetList(list, typeInfo); return; @@ -2528,9 +2515,8 @@ private void Set(object value, TypeInfo typeInfo) } // handle matrix. - Matrix matrix = value as Matrix; - if (matrix != null) + if (value is Matrix matrix) { m_value = matrix; m_typeInfo = matrix.TypeInfo; diff --git a/Stack/Opc.Ua.Core/Types/Constants/Attributes.Helpers.cs b/Stack/Opc.Ua.Core/Types/Constants/Attributes.Helpers.cs index 49329ceb7..26329bac3 100644 --- a/Stack/Opc.Ua.Core/Types/Constants/Attributes.Helpers.cs +++ b/Stack/Opc.Ua.Core/Types/Constants/Attributes.Helpers.cs @@ -15,18 +15,18 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.Xml; namespace Opc.Ua -{ - /// - /// A class that defines constants used by UA applications. - /// +{ + /// + /// A class that defines constants used by UA applications. + /// public static partial class Attributes - { + { #region Static Helper Functions /// - /// Returns true is the attribute id is valid. - /// + /// Returns true is the attribute id is valid. + /// public static bool IsValid(uint attributeId) - { + { return (attributeId >= Attributes.NodeId && attributeId <= Attributes.AccessLevelEx); } @@ -34,97 +34,97 @@ public static bool IsValid(uint attributeId) /// Returns the browse name for the attribute. /// public static string GetBrowseName(uint identifier) - { - FieldInfo[] fields = typeof(Attributes).GetFields(BindingFlags.Public | BindingFlags.Static); + { + FieldInfo[] fields = typeof(Attributes).GetFields(BindingFlags.Public | BindingFlags.Static); - foreach (FieldInfo field in fields) - { + foreach (FieldInfo field in fields) + { if (identifier == (uint)field.GetValue(typeof(Attributes))) - { - return field.Name; - } - } + { + return field.Name; + } + } - return System.String.Empty; - } + return System.String.Empty; + } + + /// + /// Returns the browse names for all attributes. + /// + public static string[] GetBrowseNames() + { + FieldInfo[] fields = typeof(Attributes).GetFields(BindingFlags.Public | BindingFlags.Static); - /// - /// Returns the browse names for all attributes. - /// - public static string[] GetBrowseNames() - { - FieldInfo[] fields = typeof(Attributes).GetFields(BindingFlags.Public | BindingFlags.Static); - int ii = 0; string[] names = new string[fields.Length]; - - foreach (FieldInfo field in fields) - { - names[ii++] = field.Name; - } - return names; - } + foreach (FieldInfo field in fields) + { + names[ii++] = field.Name; + } - /// - /// Returns the id for the attribute with the specified browse name. - /// + return names; + } + + /// + /// Returns the id for the attribute with the specified browse name. + /// public static uint GetIdentifier(string browseName) - { - FieldInfo[] fields = typeof(Attributes).GetFields(BindingFlags.Public | BindingFlags.Static); + { + FieldInfo[] fields = typeof(Attributes).GetFields(BindingFlags.Public | BindingFlags.Static); - foreach (FieldInfo field in fields) - { - if (field.Name == browseName) - { + foreach (FieldInfo field in fields) + { + if (field.Name == browseName) + { return (uint)field.GetValue(typeof(Attributes)); - } - } + } + } - return 0; + return 0; } - /// - /// Returns the ids for all attributes. - /// - public static uint[] GetIdentifiers() - { - FieldInfo[] fields = typeof(Attributes).GetFields(BindingFlags.Public | BindingFlags.Static); - + /// + /// Returns the ids for all attributes. + /// + public static uint[] GetIdentifiers() + { + FieldInfo[] fields = typeof(Attributes).GetFields(BindingFlags.Public | BindingFlags.Static); + int ii = 0; uint[] ids = new uint[fields.Length]; - - foreach (FieldInfo field in fields) - { + + foreach (FieldInfo field in fields) + { ids[ii++] = (uint)field.GetValue(typeof(Attributes)); - } + } - return ids; - } - - /// - /// Returns the ids for all attributes which are valid for the at least one of the node classes specified by the mask. - /// + return ids; + } + + /// + /// Returns the ids for all attributes which are valid for the at least one of the node classes specified by the mask. + /// public static UInt32Collection GetIdentifiers(NodeClass nodeClass) { - FieldInfo[] fields = typeof(Attributes).GetFields(BindingFlags.Public | BindingFlags.Static); - + FieldInfo[] fields = typeof(Attributes).GetFields(BindingFlags.Public | BindingFlags.Static); + UInt32Collection ids = new UInt32Collection(fields.Length); - - foreach (FieldInfo field in fields) - { + + foreach (FieldInfo field in fields) + { uint id = (uint)field.GetValue(typeof(Attributes)); if (IsValid(nodeClass, id)) { ids.Add(id); } - } + } - return ids; + return ids; } - + /// /// Returns the built type required for an attribute. /// @@ -132,38 +132,38 @@ public static BuiltInType GetBuiltInType(uint attributeId) { switch (attributeId) { - case Value: return BuiltInType.Variant; - case DisplayName: return BuiltInType.LocalizedText; - case Description: return BuiltInType.LocalizedText; - case WriteMask: return BuiltInType.UInt32; - case UserWriteMask: return BuiltInType.UInt32; - case NodeId: return BuiltInType.NodeId; - case NodeClass: return BuiltInType.Int32; - case BrowseName: return BuiltInType.QualifiedName; - case IsAbstract: return BuiltInType.Boolean; - case Symmetric: return BuiltInType.Boolean; - case InverseName: return BuiltInType.LocalizedText; - case ContainsNoLoops: return BuiltInType.Boolean; - case EventNotifier: return BuiltInType.Byte; - case DataType: return BuiltInType.NodeId; - case ValueRank: return BuiltInType.Int32; - case AccessLevel: return BuiltInType.Byte; - case UserAccessLevel: return BuiltInType.Byte; + case Value: return BuiltInType.Variant; + case DisplayName: return BuiltInType.LocalizedText; + case Description: return BuiltInType.LocalizedText; + case WriteMask: return BuiltInType.UInt32; + case UserWriteMask: return BuiltInType.UInt32; + case NodeId: return BuiltInType.NodeId; + case NodeClass: return BuiltInType.Int32; + case BrowseName: return BuiltInType.QualifiedName; + case IsAbstract: return BuiltInType.Boolean; + case Symmetric: return BuiltInType.Boolean; + case InverseName: return BuiltInType.LocalizedText; + case ContainsNoLoops: return BuiltInType.Boolean; + case EventNotifier: return BuiltInType.Byte; + case DataType: return BuiltInType.NodeId; + case ValueRank: return BuiltInType.Int32; + case AccessLevel: return BuiltInType.Byte; + case UserAccessLevel: return BuiltInType.Byte; case MinimumSamplingInterval: return BuiltInType.Double; - case Historizing: return BuiltInType.Boolean; - case Executable: return BuiltInType.Boolean; - case UserExecutable: return BuiltInType.Boolean; - case ArrayDimensions: return BuiltInType.UInt32; - case DataTypeDefinition: return BuiltInType.ExtensionObject; - case RolePermissions: return BuiltInType.Variant; - case UserRolePermissions: return BuiltInType.Variant; - case AccessRestrictions: return BuiltInType.UInt16; - case AccessLevelEx: return BuiltInType.UInt32; + case Historizing: return BuiltInType.Boolean; + case Executable: return BuiltInType.Boolean; + case UserExecutable: return BuiltInType.Boolean; + case ArrayDimensions: return BuiltInType.UInt32; + case DataTypeDefinition: return BuiltInType.ExtensionObject; + case RolePermissions: return BuiltInType.Variant; + case UserRolePermissions: return BuiltInType.Variant; + case AccessRestrictions: return BuiltInType.UInt16; + case AccessLevelEx: return BuiltInType.UInt32; } - + return BuiltInType.Null; } - + /// /// Returns the data type id for the attribute. /// @@ -171,38 +171,38 @@ public static NodeId GetDataTypeId(uint attributeId) { switch (attributeId) { - case Value: return DataTypes.BaseDataType; - case DisplayName: return DataTypes.LocalizedText; - case Description: return DataTypes.LocalizedText; - case WriteMask: return DataTypes.UInt32; - case UserWriteMask: return DataTypes.UInt32; - case NodeId: return DataTypes.NodeId; - case NodeClass: return DataTypes.Enumeration; - case BrowseName: return DataTypes.QualifiedName; - case IsAbstract: return DataTypes.Boolean; - case Symmetric: return DataTypes.Boolean; - case InverseName: return DataTypes.LocalizedText; - case ContainsNoLoops: return DataTypes.Boolean; - case EventNotifier: return DataTypes.Byte; - case DataType: return DataTypes.NodeId; - case ValueRank: return DataTypes.Int32; - case AccessLevel: return DataTypes.Byte; - case UserAccessLevel: return DataTypes.Byte; + case Value: return DataTypes.BaseDataType; + case DisplayName: return DataTypes.LocalizedText; + case Description: return DataTypes.LocalizedText; + case WriteMask: return DataTypes.UInt32; + case UserWriteMask: return DataTypes.UInt32; + case NodeId: return DataTypes.NodeId; + case NodeClass: return DataTypes.Enumeration; + case BrowseName: return DataTypes.QualifiedName; + case IsAbstract: return DataTypes.Boolean; + case Symmetric: return DataTypes.Boolean; + case InverseName: return DataTypes.LocalizedText; + case ContainsNoLoops: return DataTypes.Boolean; + case EventNotifier: return DataTypes.Byte; + case DataType: return DataTypes.NodeId; + case ValueRank: return DataTypes.Int32; + case AccessLevel: return DataTypes.Byte; + case UserAccessLevel: return DataTypes.Byte; case MinimumSamplingInterval: return DataTypes.Duration; - case Historizing: return DataTypes.Boolean; - case Executable: return DataTypes.Boolean; - case UserExecutable: return DataTypes.Boolean; - case ArrayDimensions: return DataTypes.UInt32; - case DataTypeDefinition: return DataTypes.Structure; - case RolePermissions: return DataTypes.RolePermissionType; - case UserRolePermissions: return DataTypes.RolePermissionType; - case AccessRestrictions: return DataTypes.UInt16; - case AccessLevelEx: return DataTypes.UInt32; + case Historizing: return DataTypes.Boolean; + case Executable: return DataTypes.Boolean; + case UserExecutable: return DataTypes.Boolean; + case ArrayDimensions: return DataTypes.UInt32; + case DataTypeDefinition: return DataTypes.Structure; + case RolePermissions: return DataTypes.RolePermissionType; + case UserRolePermissions: return DataTypes.RolePermissionType; + case AccessRestrictions: return DataTypes.UInt16; + case AccessLevelEx: return DataTypes.UInt32; } - + return null; } - + /// /// Returns true if the corresponding bit is set in the attribute write mask. /// @@ -210,37 +210,37 @@ public static bool IsWriteable(uint attributeId, uint writeMask) { switch (attributeId) { - case Value: return (writeMask & (uint)AttributeWriteMask.ValueForVariableType) != 0; - case DisplayName: return (writeMask & (uint)AttributeWriteMask.DisplayName) != 0; - case Description: return (writeMask & (uint)AttributeWriteMask.Description) != 0; - case WriteMask: return (writeMask & (uint)AttributeWriteMask.WriteMask) != 0; - case UserWriteMask: return (writeMask & (uint)AttributeWriteMask.UserWriteMask) != 0; - case NodeId: return (writeMask & (uint)AttributeWriteMask.NodeId) != 0; - case NodeClass: return (writeMask & (uint)AttributeWriteMask.NodeClass) != 0; - case BrowseName: return (writeMask & (uint)AttributeWriteMask.BrowseName) != 0; - case IsAbstract: return (writeMask & (uint)AttributeWriteMask.IsAbstract) != 0; - case Symmetric: return (writeMask & (uint)AttributeWriteMask.Symmetric) != 0; - case InverseName: return (writeMask & (uint)AttributeWriteMask.InverseName) != 0; - case ContainsNoLoops: return (writeMask & (uint)AttributeWriteMask.ContainsNoLoops) != 0; - case EventNotifier: return (writeMask & (uint)AttributeWriteMask.EventNotifier) != 0; - case DataType: return (writeMask & (uint)AttributeWriteMask.DataType) != 0; - case ValueRank: return (writeMask & (uint)AttributeWriteMask.ValueRank) != 0; - case AccessLevel: return (writeMask & (uint)AttributeWriteMask.AccessLevel) != 0; - case UserAccessLevel: return (writeMask & (uint)AttributeWriteMask.UserAccessLevel) != 0; + case Value: return (writeMask & (uint)AttributeWriteMask.ValueForVariableType) != 0; + case DisplayName: return (writeMask & (uint)AttributeWriteMask.DisplayName) != 0; + case Description: return (writeMask & (uint)AttributeWriteMask.Description) != 0; + case WriteMask: return (writeMask & (uint)AttributeWriteMask.WriteMask) != 0; + case UserWriteMask: return (writeMask & (uint)AttributeWriteMask.UserWriteMask) != 0; + case NodeId: return (writeMask & (uint)AttributeWriteMask.NodeId) != 0; + case NodeClass: return (writeMask & (uint)AttributeWriteMask.NodeClass) != 0; + case BrowseName: return (writeMask & (uint)AttributeWriteMask.BrowseName) != 0; + case IsAbstract: return (writeMask & (uint)AttributeWriteMask.IsAbstract) != 0; + case Symmetric: return (writeMask & (uint)AttributeWriteMask.Symmetric) != 0; + case InverseName: return (writeMask & (uint)AttributeWriteMask.InverseName) != 0; + case ContainsNoLoops: return (writeMask & (uint)AttributeWriteMask.ContainsNoLoops) != 0; + case EventNotifier: return (writeMask & (uint)AttributeWriteMask.EventNotifier) != 0; + case DataType: return (writeMask & (uint)AttributeWriteMask.DataType) != 0; + case ValueRank: return (writeMask & (uint)AttributeWriteMask.ValueRank) != 0; + case AccessLevel: return (writeMask & (uint)AttributeWriteMask.AccessLevel) != 0; + case UserAccessLevel: return (writeMask & (uint)AttributeWriteMask.UserAccessLevel) != 0; case MinimumSamplingInterval: return (writeMask & (uint)AttributeWriteMask.MinimumSamplingInterval) != 0; - case Historizing: return (writeMask & (uint)AttributeWriteMask.Historizing) != 0; - case Executable: return (writeMask & (uint)AttributeWriteMask.Executable) != 0; - case UserExecutable: return (writeMask & (uint)AttributeWriteMask.UserExecutable) != 0; - case ArrayDimensions: return (writeMask & (uint)AttributeWriteMask.ArrayDimensions) != 0; - case DataTypeDefinition: return (writeMask & (uint)AttributeWriteMask.DataTypeDefinition) != 0; - case RolePermissions: return (writeMask & (uint)AttributeWriteMask.RolePermissions) != 0; - case AccessRestrictions: return (writeMask & (uint)AttributeWriteMask.AccessRestrictions) != 0; - case AccessLevelEx: return (writeMask & (uint)AttributeWriteMask.AccessLevelEx) != 0; + case Historizing: return (writeMask & (uint)AttributeWriteMask.Historizing) != 0; + case Executable: return (writeMask & (uint)AttributeWriteMask.Executable) != 0; + case UserExecutable: return (writeMask & (uint)AttributeWriteMask.UserExecutable) != 0; + case ArrayDimensions: return (writeMask & (uint)AttributeWriteMask.ArrayDimensions) != 0; + case DataTypeDefinition: return (writeMask & (uint)AttributeWriteMask.DataTypeDefinition) != 0; + case RolePermissions: return (writeMask & (uint)AttributeWriteMask.RolePermissions) != 0; + case AccessRestrictions: return (writeMask & (uint)AttributeWriteMask.AccessRestrictions) != 0; + case AccessLevelEx: return (writeMask & (uint)AttributeWriteMask.AccessLevelEx) != 0; } - + return false; } - + /// /// Sets the corresponding bit in the attribute write mask and returns the result. /// @@ -248,37 +248,37 @@ public static uint SetWriteable(uint attributeId, uint writeMask) { switch (attributeId) { - case Value: return writeMask | (uint)AttributeWriteMask.ValueForVariableType; - case DisplayName: return writeMask | (uint)AttributeWriteMask.DisplayName; - case Description: return writeMask | (uint)AttributeWriteMask.Description; - case WriteMask: return writeMask | (uint)AttributeWriteMask.WriteMask; - case UserWriteMask: return writeMask | (uint)AttributeWriteMask.UserWriteMask; - case NodeId: return writeMask | (uint)AttributeWriteMask.NodeId; - case NodeClass: return writeMask | (uint)AttributeWriteMask.NodeClass; - case BrowseName: return writeMask | (uint)AttributeWriteMask.BrowseName; - case IsAbstract: return writeMask | (uint)AttributeWriteMask.IsAbstract; - case Symmetric: return writeMask | (uint)AttributeWriteMask.Symmetric; - case InverseName: return writeMask | (uint)AttributeWriteMask.InverseName; - case ContainsNoLoops: return writeMask | (uint)AttributeWriteMask.ContainsNoLoops; - case EventNotifier: return writeMask | (uint)AttributeWriteMask.EventNotifier; - case DataType: return writeMask | (uint)AttributeWriteMask.DataType; - case ValueRank: return writeMask | (uint)AttributeWriteMask.ValueRank; - case AccessLevel: return writeMask | (uint)AttributeWriteMask.AccessLevel; - case UserAccessLevel: return writeMask | (uint)AttributeWriteMask.UserAccessLevel; + case Value: return writeMask | (uint)AttributeWriteMask.ValueForVariableType; + case DisplayName: return writeMask | (uint)AttributeWriteMask.DisplayName; + case Description: return writeMask | (uint)AttributeWriteMask.Description; + case WriteMask: return writeMask | (uint)AttributeWriteMask.WriteMask; + case UserWriteMask: return writeMask | (uint)AttributeWriteMask.UserWriteMask; + case NodeId: return writeMask | (uint)AttributeWriteMask.NodeId; + case NodeClass: return writeMask | (uint)AttributeWriteMask.NodeClass; + case BrowseName: return writeMask | (uint)AttributeWriteMask.BrowseName; + case IsAbstract: return writeMask | (uint)AttributeWriteMask.IsAbstract; + case Symmetric: return writeMask | (uint)AttributeWriteMask.Symmetric; + case InverseName: return writeMask | (uint)AttributeWriteMask.InverseName; + case ContainsNoLoops: return writeMask | (uint)AttributeWriteMask.ContainsNoLoops; + case EventNotifier: return writeMask | (uint)AttributeWriteMask.EventNotifier; + case DataType: return writeMask | (uint)AttributeWriteMask.DataType; + case ValueRank: return writeMask | (uint)AttributeWriteMask.ValueRank; + case AccessLevel: return writeMask | (uint)AttributeWriteMask.AccessLevel; + case UserAccessLevel: return writeMask | (uint)AttributeWriteMask.UserAccessLevel; case MinimumSamplingInterval: return writeMask | (uint)AttributeWriteMask.MinimumSamplingInterval; - case Historizing: return writeMask | (uint)AttributeWriteMask.Historizing; - case Executable: return writeMask | (uint)AttributeWriteMask.Executable; - case UserExecutable: return writeMask | (uint)AttributeWriteMask.UserExecutable; - case ArrayDimensions: return writeMask | (uint)AttributeWriteMask.ArrayDimensions; - case DataTypeDefinition: return writeMask | (uint)AttributeWriteMask.DataTypeDefinition; - case RolePermissions: return writeMask | (uint)AttributeWriteMask.RolePermissions; - case AccessRestrictions: return writeMask | (uint)AttributeWriteMask.AccessRestrictions; - case AccessLevelEx: return writeMask | (uint)AttributeWriteMask.AccessLevelEx; + case Historizing: return writeMask | (uint)AttributeWriteMask.Historizing; + case Executable: return writeMask | (uint)AttributeWriteMask.Executable; + case UserExecutable: return writeMask | (uint)AttributeWriteMask.UserExecutable; + case ArrayDimensions: return writeMask | (uint)AttributeWriteMask.ArrayDimensions; + case DataTypeDefinition: return writeMask | (uint)AttributeWriteMask.DataTypeDefinition; + case RolePermissions: return writeMask | (uint)AttributeWriteMask.RolePermissions; + case AccessRestrictions: return writeMask | (uint)AttributeWriteMask.AccessRestrictions; + case AccessLevelEx: return writeMask | (uint)AttributeWriteMask.AccessLevelEx; } return writeMask; } - + /// /// Returns the value rank for the attribute. /// @@ -288,7 +288,7 @@ public static int GetValueRank(uint attributeId) { return ValueRanks.Any; } - + if (attributeId == Attributes.ArrayDimensions) { return ValueRanks.OneDimension; @@ -345,8 +345,8 @@ public static bool IsValid(NodeClass nodeClass, uint attributeId) case EventNotifier: { return (nodeClass & (Opc.Ua.NodeClass.Object | Opc.Ua.NodeClass.View)) != 0; - } - + } + case AccessLevel: case UserAccessLevel: case MinimumSamplingInterval: @@ -354,7 +354,7 @@ public static bool IsValid(NodeClass nodeClass, uint attributeId) case AccessLevelEx: { return (nodeClass & Opc.Ua.NodeClass.Variable) != 0; - } + } case Executable: case UserExecutable: @@ -378,32 +378,32 @@ public static AttributeWriteMask GetMask(uint attributeId) { switch (attributeId) { - case NodeId: return AttributeWriteMask.NodeId; - case NodeClass: return AttributeWriteMask.NodeClass; - case BrowseName: return AttributeWriteMask.BrowseName; - case DisplayName: return AttributeWriteMask.DisplayName; - case Description: return AttributeWriteMask.Description; - case WriteMask: return AttributeWriteMask.WriteMask; - case UserWriteMask: return AttributeWriteMask.UserWriteMask; - case DataType: return AttributeWriteMask.DataType; - case ValueRank: return AttributeWriteMask.ValueRank; - case ArrayDimensions: return AttributeWriteMask.ArrayDimensions; - case IsAbstract: return AttributeWriteMask.IsAbstract; - case Symmetric: return AttributeWriteMask.Symmetric; - case InverseName: return AttributeWriteMask.InverseName; - case ContainsNoLoops: return AttributeWriteMask.ContainsNoLoops; - case EventNotifier: return AttributeWriteMask.EventNotifier; - case AccessLevel: return AttributeWriteMask.AccessLevel; - case UserAccessLevel: return AttributeWriteMask.UserAccessLevel; + case NodeId: return AttributeWriteMask.NodeId; + case NodeClass: return AttributeWriteMask.NodeClass; + case BrowseName: return AttributeWriteMask.BrowseName; + case DisplayName: return AttributeWriteMask.DisplayName; + case Description: return AttributeWriteMask.Description; + case WriteMask: return AttributeWriteMask.WriteMask; + case UserWriteMask: return AttributeWriteMask.UserWriteMask; + case DataType: return AttributeWriteMask.DataType; + case ValueRank: return AttributeWriteMask.ValueRank; + case ArrayDimensions: return AttributeWriteMask.ArrayDimensions; + case IsAbstract: return AttributeWriteMask.IsAbstract; + case Symmetric: return AttributeWriteMask.Symmetric; + case InverseName: return AttributeWriteMask.InverseName; + case ContainsNoLoops: return AttributeWriteMask.ContainsNoLoops; + case EventNotifier: return AttributeWriteMask.EventNotifier; + case AccessLevel: return AttributeWriteMask.AccessLevel; + case UserAccessLevel: return AttributeWriteMask.UserAccessLevel; case MinimumSamplingInterval: return AttributeWriteMask.MinimumSamplingInterval; - case Historizing: return AttributeWriteMask.Historizing; - case Executable: return AttributeWriteMask.Executable; - case UserExecutable: return AttributeWriteMask.UserExecutable; - case DataTypeDefinition: return AttributeWriteMask.DataTypeDefinition; - case RolePermissions: return AttributeWriteMask.RolePermissions; + case Historizing: return AttributeWriteMask.Historizing; + case Executable: return AttributeWriteMask.Executable; + case UserExecutable: return AttributeWriteMask.UserExecutable; + case DataTypeDefinition: return AttributeWriteMask.DataTypeDefinition; + case RolePermissions: return AttributeWriteMask.RolePermissions; //case UserRolePermissions: return AttributeWriteMask.UserRolePermissions; - case AccessRestrictions: return AttributeWriteMask.AccessRestrictions; - case AccessLevelEx: return AttributeWriteMask.AccessLevelEx; + case AccessRestrictions: return AttributeWriteMask.AccessRestrictions; + case AccessLevelEx: return AttributeWriteMask.AccessLevelEx; } return 0; diff --git a/Stack/Opc.Ua.Core/Types/Encoders/BinaryDecoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/BinaryDecoder.cs index eb9e270e7..199df4bf7 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/BinaryDecoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/BinaryDecoder.cs @@ -722,9 +722,8 @@ public IEncodeable ReadEncodeable(string fieldName, System.Type systemType, Expa { if (systemType == null) throw new ArgumentNullException(nameof(systemType)); - IEncodeable encodeable = Activator.CreateInstance(systemType) as IEncodeable; - if (encodeable == null) + if (!(Activator.CreateInstance(systemType) is IEncodeable encodeable)) { throw new ServiceResultException( StatusCodes.BadDecodingError, @@ -734,9 +733,8 @@ public IEncodeable ReadEncodeable(string fieldName, System.Type systemType, Expa if (encodeableTypeId != null) { // set type identifier for custom complex data types before decode. - IComplexTypeInstance complexTypeInstance = encodeable as IComplexTypeInstance; - if (complexTypeInstance != null) + if (encodeable is IComplexTypeInstance complexTypeInstance) { complexTypeInstance.TypeId = encodeableTypeId; } @@ -2098,9 +2096,8 @@ private ExtensionObject ReadExtensionObject() encodeable = Activator.CreateInstance(systemType) as IEncodeable; // set type identifier for custom complex data types before decode. - IComplexTypeInstance complexTypeInstance = encodeable as IComplexTypeInstance; - if (complexTypeInstance != null) + if (encodeable is IComplexTypeInstance complexTypeInstance) { complexTypeInstance.TypeId = extension.TypeId; } @@ -2126,22 +2123,31 @@ private ExtensionObject ReadExtensionObject() // verify the decoder did not exceed the length of the encodeable object int used = Position - start; - if (length < used) + if (length != used) { throw ServiceResultException.Create( - StatusCodes.BadEncodingLimitsExceeded, - "The encodeable.Decoder operation exceeded the length of the extension object. {0} > {1}", + StatusCodes.BadDecodingError, + "The encodeable.Decoder operation did not match the length of the extension object. {0} != {1}", used, length); } } - catch (ServiceResultException sre) when (sre.StatusCode == StatusCodes.BadEncodingLimitsExceeded) + catch (EndOfStreamException eofStream) { // type was known but decoding failed, reset stream! m_reader.BaseStream.Position = start; encodeable = null; - Utils.LogWarning(sre, "Failed to decode encodeable type '{0}', NodeId='{1}'. BinaryDecoder recovered.", + Utils.LogWarning(eofStream, "End of stream, failed to decode encodeable type '{0}', NodeId='{1}'. BinaryDecoder recovered.", systemType.Name, extension.TypeId); } + catch (ServiceResultException sre) when + ((sre.StatusCode == StatusCodes.BadEncodingLimitsExceeded) || (sre.StatusCode == StatusCodes.BadDecodingError)) + { + // type was known but decoding failed, reset stream! + m_reader.BaseStream.Position = start; + encodeable = null; + Utils.LogWarning(sre, "{0}, failed to decode encodeable type '{1}', NodeId='{2}'. BinaryDecoder recovered.", + sre.Message, systemType.Name, extension.TypeId); + } finally { m_nestingLevel = nestingLevel; diff --git a/Stack/Opc.Ua.Core/Types/Encoders/BinaryEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/BinaryEncoder.cs index 58a72292c..3e3dd869b 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/BinaryEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/BinaryEncoder.cs @@ -90,11 +90,13 @@ protected virtual void Dispose(bool disposing) { m_writer.Flush(); m_writer.Dispose(); + m_writer = null; } if (!m_leaveOpen) { m_ostrm?.Dispose(); + m_ostrm = null; } } } @@ -440,7 +442,7 @@ public void WriteString(string fieldName, string value) return; } - byte[] bytes = new UTF8Encoding().GetBytes(value); + byte[] bytes = Encoding.UTF8.GetBytes(value); if (m_context.MaxStringLength > 0 && m_context.MaxStringLength < bytes.Length) { @@ -451,7 +453,7 @@ public void WriteString(string fieldName, string value) bytes.Length); } - WriteByteString(null, new UTF8Encoding().GetBytes(value)); + WriteByteString(null, Encoding.UTF8.GetBytes(value)); } /// @@ -534,7 +536,7 @@ public void WriteXmlElement(string fieldName, XmlElement value) return; } - WriteByteString(null, new UTF8Encoding().GetBytes(value.OuterXml)); + WriteByteString(null, Encoding.UTF8.GetBytes(value.OuterXml)); } /// @@ -874,18 +876,16 @@ public void WriteExtensionObject(string fieldName, ExtensionObject value) } // write binary bodies. - byte[] bytes = body as byte[]; - if (bytes != null) + if (body is byte[] bytes) { WriteByteString(null, bytes); return; } // write XML bodies. - XmlElement xml = body as XmlElement; - if (xml != null) + if (body is XmlElement xml) { WriteXmlElement(null, xml); return; @@ -1548,8 +1548,7 @@ public void WriteArray(string fieldName, object array, int valueRank, BuiltInTyp case BuiltInType.Variant: { // try to write IEncodeable Array - IEncodeable[] encodeableArray = array as IEncodeable[]; - if (encodeableArray != null) + if (array is IEncodeable[] encodeableArray) { WriteEncodeableArray(fieldName, encodeableArray, array.GetType().GetElementType()); return; @@ -1561,8 +1560,7 @@ public void WriteArray(string fieldName, object array, int valueRank, BuiltInTyp int[] ints = array as int[]; if (ints == null) { - Enum[] enums = array as Enum[]; - if (enums != null) + if (array is Enum[] enums) { ints = new int[enums.Length]; for (int ii = 0; ii < enums.Length; ii++) @@ -1594,8 +1592,7 @@ public void WriteArray(string fieldName, object array, int valueRank, BuiltInTyp default: { // try to write IEncodeable Array - IEncodeable[] encodeableArray = array as IEncodeable[]; - if (encodeableArray != null) + if (array is IEncodeable[] encodeableArray) { WriteEncodeableArray(fieldName, encodeableArray, array.GetType().GetElementType()); break; @@ -1623,8 +1620,7 @@ public void WriteArray(string fieldName, object array, int valueRank, BuiltInTyp Matrix matrix = array as Matrix; if (matrix == null) { - var multiArray = array as Array; - if (multiArray == null || multiArray.Rank != valueRank) + if (!(array is Array multiArray) || multiArray.Rank != valueRank) { // there is no Dimensions to write WriteInt32(null, -1); @@ -1685,8 +1681,7 @@ public void WriteArray(string fieldName, object array, int valueRank, BuiltInTyp } case BuiltInType.Enumeration: { - Enum[] values = matrix.Elements as Enum[]; - if (values != null) + if (matrix.Elements is Enum[] values) { for (int ii = 0; ii < values.Length; ii++) { @@ -1860,8 +1855,7 @@ public void WriteArray(string fieldName, object array, int valueRank, BuiltInTyp } case BuiltInType.Variant: { - Variant[] variants = matrix.Elements as Variant[]; - if (variants != null) + if (matrix.Elements is Variant[] variants) { for (int ii = 0; ii < variants.Length; ii++) { @@ -1871,8 +1865,7 @@ public void WriteArray(string fieldName, object array, int valueRank, BuiltInTyp } // try to write IEncodeable Array - IEncodeable[] encodeableArray = matrix.Elements as IEncodeable[]; - if (encodeableArray != null) + if (matrix.Elements is IEncodeable[] encodeableArray) { for (int ii = 0; ii < encodeableArray.Length; ii++) { @@ -1881,8 +1874,7 @@ public void WriteArray(string fieldName, object array, int valueRank, BuiltInTyp break; } - object[] objects = matrix.Elements as object[]; - if (objects != null) + if (matrix.Elements is object[] objects) { for (int ii = 0; ii < objects.Length; ii++) { @@ -1906,8 +1898,7 @@ public void WriteArray(string fieldName, object array, int valueRank, BuiltInTyp default: { // try to write IEncodeable Array - IEncodeable[] encodeableArray = matrix.Elements as IEncodeable[]; - if (encodeableArray != null) + if (matrix.Elements is IEncodeable[] encodeableArray) { for (int ii = 0; ii < encodeableArray.Length; ii++) { @@ -2320,11 +2311,9 @@ private void WriteVariantValue(string fieldName, Variant value) case BuiltInType.Enumeration: { // Check whether the value to encode is int array. - int[] ints = valueToEncode as int[]; - if (ints == null) + if (!(valueToEncode is int[] ints)) { - Enum[] enums = valueToEncode as Enum[]; - if (enums == null) + if (!(valueToEncode is Enum[] enums)) { throw new ServiceResultException( StatusCodes.BadEncodingError, @@ -2343,17 +2332,14 @@ private void WriteVariantValue(string fieldName, Variant value) case BuiltInType.Variant: { - Variant[] variants = valueToEncode as Variant[]; - - if (variants != null) + if (valueToEncode is Variant[] variants) { WriteVariantArray(null, variants); break; } - object[] objects = valueToEncode as object[]; - if (objects != null) + if (valueToEncode is object[] objects) { WriteObjectArray(null, objects); break; diff --git a/Stack/Opc.Ua.Core/Types/Encoders/EncodableObject.cs b/Stack/Opc.Ua.Core/Types/Encoders/EncodableObject.cs index 9bcfe2a9f..0d739a4ed 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/EncodableObject.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/EncodableObject.cs @@ -81,9 +81,8 @@ public static ServiceResult ApplyDataEncoding(IServiceMessageContext context, Qu if (encodeables == null) { // check for array of extension objects. - IList extensions = value as IList; - if (extensions != null) + if (value is IList extensions) { // convert extension objects to encodeables. encodeables = new IEncodeable[extensions.Count]; @@ -96,9 +95,8 @@ public static ServiceResult ApplyDataEncoding(IServiceMessageContext context, Qu continue; } - IEncodeable element = extensions[ii].Body as IEncodeable; - if (element == null) + if (!(extensions[ii].Body is IEncodeable element)) { return StatusCodes.BadTypeMismatch; } @@ -127,9 +125,7 @@ public static ServiceResult ApplyDataEncoding(IServiceMessageContext context, Qu if (encodeable == null) { - ExtensionObject extension = value as ExtensionObject; - - if (extension == null) + if (!(value is ExtensionObject extension)) { return StatusCodes.BadDataEncodingUnsupported; } diff --git a/Stack/Opc.Ua.Core/Types/Encoders/EncodeableFactory.cs b/Stack/Opc.Ua.Core/Types/Encoders/EncodeableFactory.cs index bebe73356..e669bc0b9 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/EncodeableFactory.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/EncodeableFactory.cs @@ -12,6 +12,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.Serialization; @@ -72,15 +73,31 @@ public EncodeableFactory(IEncodeableFactory factory) #if DEBUG m_instanceId = Interlocked.Increment(ref m_globalInstanceCount); #endif + if (factory != null) + { + m_encodeableTypes = ((EncodeableFactory)factory.Clone()).m_encodeableTypes; + } + } + #endregion - lock (factory.SyncRoot) + #region IDisposable + /// + /// An overrideable version of the Dispose. + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) { - foreach (KeyValuePair current in factory.EncodeableTypes) - { - m_encodeableTypes.Add(current.Key, current.Value); - } + m_readerWriterLockSlim?.Dispose(); } } + + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } #endregion #region Private Members @@ -108,61 +125,83 @@ private void AddEncodeableTypes(string assemblyName) /// A dictionary of unbound typeIds, e.g. JSON type ids referenced by object name. private void AddEncodeableType(Type systemType, Dictionary unboundTypeIds) { - lock (m_lock) + if (systemType == null) { - if (systemType == null) - { - return; - } + return; + } - if (!typeof(IEncodeable).GetTypeInfo().IsAssignableFrom(systemType.GetTypeInfo())) - { - return; - } + if (!typeof(IEncodeable).GetTypeInfo().IsAssignableFrom(systemType.GetTypeInfo())) + { + return; + } - IEncodeable encodeable = Activator.CreateInstance(systemType) as IEncodeable; - if (encodeable == null) - { - return; - } + if (!(Activator.CreateInstance(systemType) is IEncodeable encodeable)) + { + return; + } #if DEBUG - if (m_shared) - { - Utils.LogTrace("WARNING: Adding type '{0}' to shared Factory #{1}.", systemType.Name, m_instanceId); - } + if (m_shared) + { + Utils.LogTrace("WARNING: Adding type '{0}' to shared Factory #{1}.", systemType.Name, m_instanceId); + } #endif - ExpandedNodeId nodeId = encodeable.TypeId; + // assume write lock + Debug.Assert(m_readerWriterLockSlim.IsWriteLockHeld); - if (!NodeId.IsNull(nodeId)) - { - // check for default namespace. - if (nodeId.NamespaceUri == Namespaces.OpcUa) - { - nodeId = new ExpandedNodeId(nodeId.InnerNodeId); - } + ExpandedNodeId nodeId = encodeable.TypeId; - m_encodeableTypes[nodeId] = systemType; + if (!NodeId.IsNull(nodeId)) + { + // check for default namespace. + if (nodeId.NamespaceUri == Namespaces.OpcUa) + { + nodeId = new ExpandedNodeId(nodeId.InnerNodeId); } - nodeId = encodeable.BinaryEncodingId; + m_encodeableTypes[nodeId] = systemType; + } + + nodeId = encodeable.BinaryEncodingId; - if (!NodeId.IsNull(nodeId)) + if (!NodeId.IsNull(nodeId)) + { + // check for default namespace. + if (nodeId.NamespaceUri == Namespaces.OpcUa) { - // check for default namespace. - if (nodeId.NamespaceUri == Namespaces.OpcUa) - { - nodeId = new ExpandedNodeId(nodeId.InnerNodeId); - } + nodeId = new ExpandedNodeId(nodeId.InnerNodeId); + } - m_encodeableTypes[nodeId] = systemType; + m_encodeableTypes[nodeId] = systemType; + } + + try + { + nodeId = encodeable.XmlEncodingId; + } + catch (NotSupportedException) + { + nodeId = NodeId.Null; + } + + if (!NodeId.IsNull(nodeId)) + { + // check for default namespace. + if (nodeId.NamespaceUri == Namespaces.OpcUa) + { + nodeId = new ExpandedNodeId(nodeId.InnerNodeId); } + m_encodeableTypes[nodeId] = systemType; + } + + if (encodeable is IJsonEncodeable jsonEncodeable) + { try { - nodeId = encodeable.XmlEncodingId; + nodeId = jsonEncodeable.JsonEncodingId; } catch (NotSupportedException) { @@ -179,34 +218,11 @@ private void AddEncodeableType(Type systemType, Dictionary - /// Returns the object used to synchronize access to the factory. - /// - /// - /// Returns the object used to synchronize access to the factory. - /// - public object SyncRoot - { - get { return m_lock; } - } - /// /// Returns a unique identifier for the table instance. Used to debug problems with shared tables. /// @@ -338,7 +339,15 @@ public int InstanceId /// The underlying system type to add to the factory public void AddEncodeableType(Type systemType) { - AddEncodeableType(systemType, null); + try + { + m_readerWriterLockSlim.EnterWriteLock(); + AddEncodeableType(systemType, null); + } + finally + { + m_readerWriterLockSlim.ExitWriteLock(); + } } /// @@ -348,19 +357,23 @@ public void AddEncodeableType(Type systemType) /// The system type to use for the specified encoding. public void AddEncodeableType(ExpandedNodeId encodingId, Type systemType) { - lock (m_lock) + if (systemType != null && !NodeId.IsNull(encodingId)) { - if (systemType != null && !NodeId.IsNull(encodingId)) - { #if DEBUG - if (m_shared) - { - Utils.LogWarning("WARNING: Adding type '{0}' to shared Factory #{1}.", systemType.Name, m_instanceId); - } + if (m_shared) + { + Utils.LogWarning("WARNING: Adding type '{0}' to shared Factory #{1}.", systemType.Name, m_instanceId); + } #endif - + try + { + m_readerWriterLockSlim.EnterWriteLock(); m_encodeableTypes[encodingId] = systemType; } + finally + { + m_readerWriterLockSlim.ExitWriteLock(); + } } } @@ -389,8 +402,10 @@ public void AddEncodeableTypes(Assembly assembly) } #endif - lock (m_lock) + try { + m_readerWriterLockSlim.EnterWriteLock(); + Type[] systemTypes = assembly.GetExportedTypes(); var unboundTypeIds = new Dictionary(); @@ -442,6 +457,10 @@ public void AddEncodeableTypes(Assembly assembly) // only needed while adding assembly types unboundTypeIds.Clear(); } + finally + { + m_readerWriterLockSlim.ExitWriteLock(); + } } } @@ -451,8 +470,9 @@ public void AddEncodeableTypes(Assembly assembly) /// The underlying system types to add to the factory public void AddEncodeableTypes(IEnumerable systemTypes) { - lock (m_lock) + try { + m_readerWriterLockSlim.EnterWriteLock(); foreach (var type in systemTypes) { if (type.GetTypeInfo().IsAbstract) @@ -460,9 +480,13 @@ public void AddEncodeableTypes(IEnumerable systemTypes) continue; } - AddEncodeableType(type); + AddEncodeableType(type, null); } } + finally + { + m_readerWriterLockSlim.ExitWriteLock(); + } } /// @@ -474,8 +498,10 @@ public void AddEncodeableTypes(IEnumerable systemTypes) /// The type id to return the system-type of public Type GetSystemType(ExpandedNodeId typeId) { - lock (m_lock) + try { + m_readerWriterLockSlim.EnterReadLock(); + Type systemType = null; if (NodeId.IsNull(typeId) || !m_encodeableTypes.TryGetValue(typeId, out systemType)) @@ -485,6 +511,10 @@ public Type GetSystemType(ExpandedNodeId typeId) return systemType; } + finally + { + m_readerWriterLockSlim.ExitReadLock(); + } } /// @@ -493,8 +523,37 @@ public Type GetSystemType(ExpandedNodeId typeId) public IReadOnlyDictionary EncodeableTypes => m_encodeableTypes; #endregion + #region ICloneable Methods + /// + public object Clone() + { + return MemberwiseClone(); + } + + /// + public new object MemberwiseClone() + { + EncodeableFactory clone = new EncodeableFactory(null); + + try + { + m_readerWriterLockSlim.EnterReadLock(); + foreach (KeyValuePair current in m_encodeableTypes) + { + clone.m_encodeableTypes.Add(current.Key, current.Value); + } + } + finally + { + m_readerWriterLockSlim.ExitReadLock(); + } + + return clone; + } + #endregion + #region Private Fields - private object m_lock = new object(); + private readonly ReaderWriterLockSlim m_readerWriterLockSlim = new ReaderWriterLockSlim(); private Dictionary m_encodeableTypes; private static EncodeableFactory s_globalFactory = new EncodeableFactory(); diff --git a/Stack/Opc.Ua.Core/Types/Encoders/IEncodeableFactory.cs b/Stack/Opc.Ua.Core/Types/Encoders/IEncodeableFactory.cs index 66803dd11..c94715755 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/IEncodeableFactory.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/IEncodeableFactory.cs @@ -28,16 +28,8 @@ namespace Opc.Ua /// Once the types exist within the factory, these types can be then easily queried. ///
/// - public interface IEncodeableFactory + public interface IEncodeableFactory : ICloneable { - /// - /// Returns the object used to synchronize access to the factory. - /// - /// - /// Returns the object used to synchronize access to the factory. - /// - object SyncRoot { get; } - /// /// Returns a unique identifier for the table instance. Used to debug problems with shared tables. /// diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonDecoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonDecoder.cs index 8df73c32c..992acca07 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonDecoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonDecoder.cs @@ -264,6 +264,7 @@ protected virtual void Dispose(bool disposing) if (m_reader != null) { m_reader.Close(); + m_reader = null; } } } @@ -309,9 +310,8 @@ public bool ReadField(string fieldName, out object token) return true; } - var context = m_stack.Peek() as Dictionary; - if (context == null || !context.TryGetValue(fieldName, out token)) + if (!(m_stack.Peek() is Dictionary context) || !context.TryGetValue(fieldName, out token)) { return false; } @@ -492,10 +492,9 @@ public uint ReadUInt32(string fieldName) if (value == null) { - var text = token as string; uint number = 0; - if (text == null || !UInt32.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out number)) + if (!(token is string text) || !UInt32.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out number)) { return 0; } @@ -527,10 +526,9 @@ public long ReadInt64(string fieldName) if (value == null) { - var text = token as string; long number = 0; - if (text == null || !Int64.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out number)) + if (!(token is string text) || !Int64.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out number)) { return 0; } @@ -562,10 +560,9 @@ public ulong ReadUInt64(string fieldName) if (value == null) { - var text = token as string; ulong number = 0; - if (text == null || !UInt64.TryParse(text, + if (!(token is string text) || !UInt64.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out number)) { @@ -705,9 +702,8 @@ public string ReadString(string fieldName) return null; } - var value = token as string; - if (value == null) + if (!(token is string value)) { return null; } @@ -738,8 +734,7 @@ public DateTime ReadDateTime(string fieldName) return value.Value >= m_dateTimeMaxJsonValue ? DateTime.MaxValue : value.Value; } - var text = token as string; - if (text != null) + if (token is string text) { var result = XmlConvert.ToDateTime(text, XmlDateTimeSerializationMode.Utc); return result >= m_dateTimeMaxJsonValue ? DateTime.MaxValue : result; @@ -760,9 +755,8 @@ public Uuid ReadGuid(string fieldName) return Uuid.Empty; } - var value = token as string; - if (value == null) + if (!(token is string value)) { return Uuid.Empty; } @@ -787,8 +781,7 @@ public byte[] ReadByteString(string fieldName) return null; } - var value = token as string; - if (value == null) + if (!(token is string value)) { return Array.Empty(); } @@ -815,9 +808,8 @@ public XmlElement ReadXmlElement(string fieldName) return null; } - var value = token as string; - if (value == null) + if (!(token is string value)) { return null; } @@ -827,7 +819,7 @@ public XmlElement ReadXmlElement(string fieldName) if (bytes != null && bytes.Length > 0) { XmlDocument document = new XmlDocument(); - string xmlString = new UTF8Encoding().GetString(bytes, 0, bytes.Length); + string xmlString = Encoding.UTF8.GetString(bytes, 0, bytes.Length); using (XmlReader reader = XmlReader.Create(new StringReader(xmlString), Utils.DefaultXmlReaderSettings())) { @@ -852,9 +844,8 @@ public NodeId ReadNodeId(string fieldName) return NodeId.Null; } - var value = token as Dictionary; - if (value == null) + if (!(token is Dictionary value)) { return NodeId.Null; } @@ -879,8 +870,7 @@ public NodeId ReadNodeId(string fieldName) if (index == null) { - string namespaceUri = namespaceToken as string; - if (namespaceUri != null) + if (namespaceToken is string namespaceUri) { namespaceIndex = m_context.NamespaceUris.GetIndexOrAppend(namespaceUri); } @@ -940,9 +930,8 @@ public ExpandedNodeId ReadExpandedNodeId(string fieldName) return ExpandedNodeId.Null; } - var value = token as Dictionary; - if (value == null) + if (!(token is Dictionary value)) { return ExpandedNodeId.Null; } @@ -1073,9 +1062,8 @@ public QualifiedName ReadQualifiedName(string fieldName) return QualifiedName.Null; } - var value = token as Dictionary; - if (value == null) + if (!(token is Dictionary value)) { return QualifiedName.Null; } @@ -1100,8 +1088,7 @@ public QualifiedName ReadQualifiedName(string fieldName) if (index == null) { // handle non reversible encoding - string namespaceUri = namespaceToken as string; - if (namespaceUri != null) + if (namespaceToken is string namespaceUri) { namespaceIndex = m_context.NamespaceUris.GetIndexOrAppend(namespaceUri); } @@ -1138,9 +1125,8 @@ public LocalizedText ReadLocalizedText(string fieldName) string locale = null; string text = null; - var value = token as Dictionary; - if (value == null) + if (!(token is Dictionary value)) { // read non reversible encoding text = token as string; @@ -1187,9 +1173,8 @@ public Variant ReadVariant(string fieldName) return Variant.Null; } - var value = token as Dictionary; - if (value == null) + if (!(token is Dictionary value)) { return Variant.Null; } @@ -1248,9 +1233,8 @@ public DataValue ReadDataValue(string fieldName) return null; } - var value = token as Dictionary; - if (value == null) + if (!(token is Dictionary value)) { return null; } @@ -1289,9 +1273,8 @@ public ExtensionObject ReadExtensionObject(string fieldName) return extension; } - var value = token as Dictionary; - if (value == null) + if (!(token is Dictionary value)) { return extension; } @@ -1394,9 +1377,8 @@ public IEncodeable ReadEncodeable(string fieldName, System.Type systemType, Expa return null; } - IEncodeable value = Activator.CreateInstance(systemType) as IEncodeable; - if (value == null) + if (!(Activator.CreateInstance(systemType) is IEncodeable value)) { throw new ServiceResultException(StatusCodes.BadDecodingError, Utils.Format("Type does not support IEncodeable interface: '{0}'", systemType.FullName)); } @@ -1404,9 +1386,8 @@ public IEncodeable ReadEncodeable(string fieldName, System.Type systemType, Expa if (encodeableTypeId != null) { // set type identifier for custom complex data types before decode. - IComplexTypeInstance complexTypeInstance = value as IComplexTypeInstance; - if (complexTypeInstance != null) + if (value is IComplexTypeInstance complexTypeInstance) { complexTypeInstance.TypeId = encodeableTypeId; } @@ -2608,9 +2589,8 @@ private DiagnosticInfo ReadDiagnosticInfo(string fieldName, int depth) return null; } - var value = token as Dictionary; - if (value == null) + if (!(token is Dictionary value)) { return null; } @@ -2993,17 +2973,14 @@ private NodeId DefaultNodeId(IdType idType, ushort namespaceIndex) private void EncodeAsJson(JsonTextWriter writer, object value) { - var map = value as Dictionary; - - if (map != null) + if (value is Dictionary map) { EncodeAsJson(writer, map); return; } - var list = value as List; - if (list != null) + if (value is List list) { writer.WriteStartArray(); diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs index c53f13399..b4b73d486 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs @@ -320,6 +320,7 @@ protected virtual void Dispose(bool disposing) if (m_writer != null) { Close(); + m_writer = null; } if (!m_leaveOpen) @@ -1148,9 +1149,7 @@ public void WriteVariant(string fieldName, Variant value) if (UseReversibleEncoding && !isNull) { - Matrix matrix = value.Value as Matrix; - - if (matrix != null) + if (value.Value is Matrix matrix) { WriteInt32Array("Dimensions", matrix.Dimensions); } @@ -1229,8 +1228,7 @@ public void WriteExtensionObject(string fieldName, ExtensionObject value) if (!UseReversibleEncoding && encodeable != null) { // non reversible encoding, only the content of the Body field is encoded - var structureType = value.Body as IStructureTypeInfo; - if (structureType != null && + if (value.Body is IStructureTypeInfo structureType && structureType.StructureType == StructureType.Union) { encodeable.Encode(this); @@ -2341,8 +2339,7 @@ public void WriteArray(string fieldName, object array, int valueRank, BuiltInTyp case BuiltInType.DiagnosticInfo: { WriteDiagnosticInfoArray(fieldName, (DiagnosticInfo[])array); return; } case BuiltInType.Enumeration: { - Array enumArray = array as Array; - if (enumArray == null) + if (!(array is Array enumArray)) { throw ServiceResultException.Create( StatusCodes.BadEncodingError, @@ -2353,25 +2350,21 @@ public void WriteArray(string fieldName, object array, int valueRank, BuiltInTyp } case BuiltInType.Variant: { - Variant[] variants = array as Variant[]; - - if (variants != null) + if (array is Variant[] variants) { WriteVariantArray(fieldName, variants); return; } // try to write IEncodeable Array - IEncodeable[] encodeableArray = array as IEncodeable[]; - if (encodeableArray != null) + if (array is IEncodeable[] encodeableArray) { WriteEncodeableArray(fieldName, encodeableArray, array.GetType().GetElementType()); return; } - object[] objects = array as object[]; - if (objects != null) + if (array is object[] objects) { WriteObjectArray(fieldName, objects); return; @@ -2385,8 +2378,7 @@ public void WriteArray(string fieldName, object array, int valueRank, BuiltInTyp default: { // try to write IEncodeable Array - IEncodeable[] encodeableArray = array as IEncodeable[]; - if (encodeableArray != null) + if (array is IEncodeable[] encodeableArray) { WriteEncodeableArray(fieldName, encodeableArray, array.GetType().GetElementType()); return; @@ -2406,11 +2398,9 @@ public void WriteArray(string fieldName, object array, int valueRank, BuiltInTyp // write matrix. else if (valueRank > ValueRanks.OneDimension) { - Matrix matrix = array as Matrix; - if (matrix == null) + if (!(array is Matrix matrix)) { - var multiArray = array as Array; - if (multiArray != null && multiArray.Rank == valueRank) + if (array is Array multiArray && multiArray.Rank == valueRank) { matrix = new Matrix(multiArray, builtInType); } diff --git a/Stack/Opc.Ua.Core/Types/Encoders/XmlDecoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/XmlDecoder.cs index d1922aaf9..bce2ac782 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/XmlDecoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/XmlDecoder.cs @@ -1390,8 +1390,7 @@ public ExtensionObject ReadExtensionObject(string fieldName) // read end of extension object. EndField(fieldName); - IEncodeable encodeable = body as IEncodeable; - if (encodeable != null) + if (body is IEncodeable encodeable) { // Set the known TypeId for encodeables. absoluteId = encodeable.TypeId; @@ -1411,9 +1410,8 @@ public IEncodeable ReadEncodeable(string fieldName, System.Type systemType, Expa { if (systemType == null) throw new ArgumentNullException(nameof(systemType)); - IEncodeable value = Activator.CreateInstance(systemType) as IEncodeable; - if (value == null) + if (!(Activator.CreateInstance(systemType) is IEncodeable value)) { throw new ServiceResultException( StatusCodes.BadDecodingError, @@ -1423,9 +1421,8 @@ public IEncodeable ReadEncodeable(string fieldName, System.Type systemType, Expa if (encodeableTypeId != null) { // set type identifier for custom complex data types before decode. - IComplexTypeInstance complexTypeInstance = value as IComplexTypeInstance; - if (complexTypeInstance != null) + if (value is IComplexTypeInstance complexTypeInstance) { complexTypeInstance.TypeId = encodeableTypeId; } @@ -2743,8 +2740,6 @@ private Array ReadArrayElements(string fieldName, BuiltInType builtInType, Type m_reader.Read(); } - m_namespaces.Push(Namespaces.OpcUaXsd); - // process array types. switch (builtInType) @@ -2937,7 +2932,6 @@ private Array ReadArrayElements(string fieldName, BuiltInType builtInType, Type } finally { - m_namespaces.Pop(); m_nestingLevel--; } } diff --git a/Stack/Opc.Ua.Core/Types/Encoders/XmlEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/XmlEncoder.cs index 75f4c3320..0a993d532 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/XmlEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/XmlEncoder.cs @@ -1832,11 +1832,9 @@ public void WriteVariantContents(object value, TypeInfo typeInfo) case BuiltInType.Enumeration: { - int[] ints = value as int[]; - if (ints == null) + if (!(value is int[] ints)) { - Enum[] enums = value as Enum[]; - if (enums == null) + if (!(value is Enum[] enums)) { throw new ServiceResultException( StatusCodes.BadEncodingError, @@ -1855,17 +1853,14 @@ public void WriteVariantContents(object value, TypeInfo typeInfo) case BuiltInType.Variant: { - Variant[] variants = value as Variant[]; - - if (variants != null) + if (value is Variant[] variants) { WriteVariantArray("ListOfVariant", variants); return; } - object[] objects = value as object[]; - if (objects != null) + if (value is object[] objects) { WriteObjectArray("ListOfVariant", objects); return; @@ -1909,9 +1904,8 @@ public void WriteExtensionObjectBody(object body) } // encode byte body. - byte[] bytes = body as byte[]; - if (bytes != null) + if (body is byte[] bytes) { m_writer.WriteStartElement("ByteString", Namespaces.OpcUaXsd); m_writer.WriteString(Convert.ToBase64String(bytes, Base64FormattingOptions.InsertLineBreaks)); @@ -1920,8 +1914,7 @@ public void WriteExtensionObjectBody(object body) } // encode xml body. - XmlElement xml = body as XmlElement; - if (xml != null) + if (body is XmlElement xml) { using (XmlReader reader = XmlReader.Create(new StringReader(xml.OuterXml), Utils.DefaultXmlReaderSettings())) { @@ -1930,8 +1923,7 @@ public void WriteExtensionObjectBody(object body) } } - IEncodeable encodeable = body as IEncodeable; - if (encodeable == null) + if (!(body is IEncodeable encodeable)) { throw new ServiceResultException( StatusCodes.BadEncodingError, @@ -1986,115 +1978,99 @@ public void WriteArray(string fieldName, object array, int valueRank, BuiltInTyp // write array. if (valueRank == ValueRanks.OneDimension) { - try + /*One dimensional Array parameters are always encoded by wrapping the elements in a container element + * and inserting the container into the structure. The name of the container element should be the name of the parameter. + * The name of the element in the array shall be the type name.*/ + switch (builtInType) { - m_namespaces.Push(Namespaces.OpcUaXsd); - - /*One dimensional Array parameters are always encoded by wrapping the elements in a container element - * and inserting the container into the structure. The name of the container element should be the name of the parameter. - * The name of the element in the array shall be the type name.*/ - switch (builtInType) + case BuiltInType.Boolean: { WriteBooleanArray(fieldName, (bool[])array); return; } + case BuiltInType.SByte: { WriteSByteArray(fieldName, (sbyte[])array); return; } + case BuiltInType.Byte: { WriteByteArray(fieldName, (byte[])array); return; } + case BuiltInType.Int16: { WriteInt16Array(fieldName, (short[])array); return; } + case BuiltInType.UInt16: { WriteUInt16Array(fieldName, (ushort[])array); return; } + case BuiltInType.Int32: { WriteInt32Array(fieldName, (int[])array); return; } + case BuiltInType.UInt32: { WriteUInt32Array(fieldName, (uint[])array); return; } + case BuiltInType.Int64: { WriteInt64Array(fieldName, (long[])array); return; } + case BuiltInType.UInt64: { WriteUInt64Array(fieldName, (ulong[])array); return; } + case BuiltInType.Float: { WriteFloatArray(fieldName, (float[])array); return; } + case BuiltInType.Double: { WriteDoubleArray(fieldName, (double[])array); return; } + case BuiltInType.String: { WriteStringArray(fieldName, (string[])array); return; } + case BuiltInType.DateTime: { WriteDateTimeArray(fieldName, (DateTime[])array); return; } + case BuiltInType.Guid: { WriteGuidArray(fieldName, (Uuid[])array); return; } + case BuiltInType.ByteString: { WriteByteStringArray(fieldName, (byte[][])array); return; } + case BuiltInType.XmlElement: { WriteXmlElementArray(fieldName, (XmlElement[])array); return; } + case BuiltInType.NodeId: { WriteNodeIdArray(fieldName, (NodeId[])array); return; } + case BuiltInType.ExpandedNodeId: { WriteExpandedNodeIdArray(fieldName, (ExpandedNodeId[])array); return; } + case BuiltInType.StatusCode: { WriteStatusCodeArray(fieldName, (StatusCode[])array); return; } + case BuiltInType.QualifiedName: { WriteQualifiedNameArray(fieldName, (QualifiedName[])array); return; } + case BuiltInType.LocalizedText: { WriteLocalizedTextArray(fieldName, (LocalizedText[])array); return; } + case BuiltInType.ExtensionObject: { WriteExtensionObjectArray(fieldName, (ExtensionObject[])array); return; } + case BuiltInType.DataValue: { WriteDataValueArray(fieldName, (DataValue[])array); return; } + case BuiltInType.DiagnosticInfo: { WriteDiagnosticInfoArray(fieldName, (DiagnosticInfo[])array); return; } + case BuiltInType.Enumeration: { - case BuiltInType.Boolean: { WriteBooleanArray(fieldName, (bool[])array); return; } - case BuiltInType.SByte: { WriteSByteArray(fieldName, (sbyte[])array); return; } - case BuiltInType.Byte: { WriteByteArray(fieldName, (byte[])array); return; } - case BuiltInType.Int16: { WriteInt16Array(fieldName, (short[])array); return; } - case BuiltInType.UInt16: { WriteUInt16Array(fieldName, (ushort[])array); return; } - case BuiltInType.Int32: { WriteInt32Array(fieldName, (int[])array); return; } - case BuiltInType.UInt32: { WriteUInt32Array(fieldName, (uint[])array); return; } - case BuiltInType.Int64: { WriteInt64Array(fieldName, (long[])array); return; } - case BuiltInType.UInt64: { WriteUInt64Array(fieldName, (ulong[])array); return; } - case BuiltInType.Float: { WriteFloatArray(fieldName, (float[])array); return; } - case BuiltInType.Double: { WriteDoubleArray(fieldName, (double[])array); return; } - case BuiltInType.String: { WriteStringArray(fieldName, (string[])array); return; } - case BuiltInType.DateTime: { WriteDateTimeArray(fieldName, (DateTime[])array); return; } - case BuiltInType.Guid: { WriteGuidArray(fieldName, (Uuid[])array); return; } - case BuiltInType.ByteString: { WriteByteStringArray(fieldName, (byte[][])array); return; } - case BuiltInType.XmlElement: { WriteXmlElementArray(fieldName, (XmlElement[])array); return; } - case BuiltInType.NodeId: { WriteNodeIdArray(fieldName, (NodeId[])array); return; } - case BuiltInType.ExpandedNodeId: { WriteExpandedNodeIdArray(fieldName, (ExpandedNodeId[])array); return; } - case BuiltInType.StatusCode: { WriteStatusCodeArray(fieldName, (StatusCode[])array); return; } - case BuiltInType.QualifiedName: { WriteQualifiedNameArray(fieldName, (QualifiedName[])array); return; } - case BuiltInType.LocalizedText: { WriteLocalizedTextArray(fieldName, (LocalizedText[])array); return; } - case BuiltInType.ExtensionObject: { WriteExtensionObjectArray(fieldName, (ExtensionObject[])array); return; } - case BuiltInType.DataValue: { WriteDataValueArray(fieldName, (DataValue[])array); return; } - case BuiltInType.DiagnosticInfo: { WriteDiagnosticInfoArray(fieldName, (DiagnosticInfo[])array); return; } - case BuiltInType.Enumeration: + if (!(array is int[] ints)) { - int[] ints = array as int[]; - if (ints == null) + if (!(array is Enum[] enums)) { - Enum[] enums = array as Enum[]; - if (enums == null) - { - throw new ServiceResultException( - StatusCodes.BadEncodingError, - Utils.Format("Type '{0}' is not allowed in an Enumeration.", array.GetType().FullName)); - } - ints = new int[enums.Length]; - for (int ii = 0; ii < enums.Length; ii++) - { - ints[ii] = Convert.ToInt32(enums[ii], CultureInfo.InvariantCulture); - } + throw new ServiceResultException( + StatusCodes.BadEncodingError, + Utils.Format("Type '{0}' is not allowed in an Enumeration.", array.GetType().FullName)); } + ints = new int[enums.Length]; + for (int ii = 0; ii < enums.Length; ii++) + { + ints[ii] = Convert.ToInt32(enums[ii], CultureInfo.InvariantCulture); + } + } + + WriteInt32Array(fieldName, ints); + return; + } - WriteInt32Array(fieldName, ints); + case BuiltInType.Variant: + { + if (array is Variant[] variants) + { + WriteVariantArray(fieldName, variants); return; } - case BuiltInType.Variant: + // try to write IEncodeable Array + if (array is IEncodeable[] encodeableArray) { - Variant[] variants = array as Variant[]; - - if (variants != null) - { - WriteVariantArray(fieldName, variants); - return; - } - - // try to write IEncodeable Array - IEncodeable[] encodeableArray = array as IEncodeable[]; - if (encodeableArray != null) - { - WriteEncodeableArray(fieldName, encodeableArray, array.GetType().GetElementType()); - return; - } - - object[] objects = array as object[]; + WriteEncodeableArray(fieldName, encodeableArray, array.GetType().GetElementType()); + return; + } - if (objects != null) - { - WriteObjectArray(fieldName, objects); - return; - } - throw ServiceResultException.Create( - StatusCodes.BadEncodingError, - "Unexpected type encountered while encoding an array of Variants: {0}", - array.GetType()); + if (array is object[] objects) + { + WriteObjectArray(fieldName, objects); + return; } - default: - { - // try to write IEncodeable Array - IEncodeable[] encodeableArray = array as IEncodeable[]; - if (encodeableArray != null) - { - WriteEncodeableArray(fieldName, encodeableArray, array.GetType().GetElementType()); - return; - } + throw ServiceResultException.Create( + StatusCodes.BadEncodingError, + "Unexpected type encountered while encoding an array of Variants: {0}", + array.GetType()); + } - throw ServiceResultException.Create( - StatusCodes.BadEncodingError, - "Unexpected BuiltInType encountered while encoding an array: {0}", - builtInType); + default: + { + // try to write IEncodeable Array + if (array is IEncodeable[] encodeableArray) + { + WriteEncodeableArray(fieldName, encodeableArray, array.GetType().GetElementType()); + return; } + + throw ServiceResultException.Create( + StatusCodes.BadEncodingError, + "Unexpected BuiltInType encountered while encoding an array: {0}", + builtInType); } } - finally - { - m_namespaces.Pop(); - } } // write matrix. @@ -2105,11 +2081,9 @@ public void WriteArray(string fieldName, object array, int valueRank, BuiltInTyp * product of the dimensions. * The number of values is 0 if one or more dimension is less than or equal to 0.*/ - Matrix matrix = array as Matrix; - if (matrix == null) + if (!(array is Matrix matrix)) { - var multiArray = array as Array; - if (multiArray != null && multiArray.Rank == valueRank) + if (array is Array multiArray && multiArray.Rank == valueRank) { matrix = new Matrix(multiArray, builtInType); } diff --git a/Stack/Opc.Ua.Core/Types/Schemas/BinarySchemaValidator.cs b/Stack/Opc.Ua.Core/Types/Schemas/BinarySchemaValidator.cs index 113576ee7..111db9b45 100644 --- a/Stack/Opc.Ua.Core/Types/Schemas/BinarySchemaValidator.cs +++ b/Stack/Opc.Ua.Core/Types/Schemas/BinarySchemaValidator.cs @@ -16,7 +16,6 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.IO; using System.Linq; using System.Text; -using System.Threading.Tasks; using System.Xml; using System.Xml.Serialization; @@ -72,21 +71,21 @@ public BinarySchemaValidator(IDictionary importTable) : base(imp /// /// Generates the code from the contents of the address space. /// - public async Task Validate(Stream stream) + public void Validate(Stream stream) { // read and parse the file. Dictionary = (TypeDictionary)LoadInput(typeof(TypeDictionary), stream); - await Validate().ConfigureAwait(false); + Validate(); } /// /// Generates the code from the contents of the address space. /// - public async Task Validate(string inputPath) + public void Validate(string inputPath) { // read and parse the file. Dictionary = (TypeDictionary)LoadInput(typeof(TypeDictionary), inputPath); - await Validate().ConfigureAwait(false); + Validate(); } /// @@ -128,7 +127,7 @@ public override string GetSchema(string typeName) writer.Dispose(); } - return new UTF8Encoding().GetString(ostrm.ToArray(), 0, (int)ostrm.Length); + return Encoding.UTF8.GetString(ostrm.ToArray(), 0, (int)ostrm.Length); } #endregion @@ -136,7 +135,7 @@ public override string GetSchema(string typeName) /// /// Generates the code from the contents of the address space. /// - private async Task Validate() + private void Validate() { m_descriptions = new Dictionary(); m_validatedDescriptions = new List(); @@ -147,7 +146,7 @@ private async Task Validate() { foreach (ImportDirective directive in Dictionary.Import) { - await Import(directive).ConfigureAwait(false); + Import(directive); } } else @@ -156,7 +155,7 @@ private async Task Validate() if (!WellKnownDictionaries.Any(n => string.Equals(n[0], Dictionary.TargetNamespace, StringComparison.Ordinal))) { ImportDirective directive = new ImportDirective { Namespace = Namespaces.OpcUa }; - await Import(directive).ConfigureAwait(false); + Import(directive); } } @@ -187,7 +186,7 @@ private async Task Validate() /// /// Imports a dictionary identified by an import directive. /// - private async Task Import(ImportDirective directive) + private void Import(ImportDirective directive) { // check if already loaded. if (LoadedFiles.ContainsKey(directive.Namespace)) @@ -211,7 +210,7 @@ private async Task Import(ImportDirective directive) { for (int ii = 0; ii < dictionary.Import.Length; ii++) { - await Import(dictionary.Import[ii]).ConfigureAwait(false); + Import(dictionary.Import[ii]); } } @@ -271,9 +270,8 @@ private bool IsIntegerType(FieldType field) return true; } - OpaqueType opaqueType = description as OpaqueType; - if (opaqueType != null) + if (description is OpaqueType opaqueType) { if (opaqueType.LengthInBitsSpecified) { @@ -308,9 +306,8 @@ private int GetFieldLength(FieldType field) } } - EnumeratedType enumerated = description as EnumeratedType; - if (enumerated != null) + if (description is EnumeratedType enumerated) { if (enumerated.LengthInBitsSpecified) { @@ -319,9 +316,7 @@ private int GetFieldLength(FieldType field) } else { - OpaqueType opaque = description as OpaqueType; - - if (opaque != null) + if (description is OpaqueType opaque) { if (opaque.LengthInBitsSpecified) { @@ -408,9 +403,7 @@ private void ImportDescription(TypeDescription description, string targetNamespa /// private void ValidateDescription(TypeDescription description) { - OpaqueType opaque = description as OpaqueType; - - if (opaque != null) + if (description is OpaqueType opaque) { if (!opaque.LengthInBitsSpecified) { @@ -423,9 +416,8 @@ private void ValidateDescription(TypeDescription description) } } - EnumeratedType enumerated = description as EnumeratedType; - if (enumerated != null) + if (description is EnumeratedType enumerated) { if (!enumerated.LengthInBitsSpecified) @@ -434,9 +426,8 @@ private void ValidateDescription(TypeDescription description) } } - StructuredType structure = description as StructuredType; - if (structure != null) + if (description is StructuredType structure) { if (structure.Field == null || structure.Field.Length == 0) { @@ -491,24 +482,24 @@ private void ValidateField(StructuredType description, Dictionary [System.Xml.Serialization.XmlAnyElementAttribute()] - public XmlElement[] Items { - get { + public XmlElement[] Items + { + get + { return this.itemsField; } - set { + set + { this.itemsField = value; } } - + /// [System.Xml.Serialization.XmlTextAttribute()] - public string[] Text { - get { + public string[] Text + { + get + { return this.textField; } - set { + set + { this.textField = value; } } - + /// - public string[] AnyAttr { - get { + public string[] AnyAttr + { + get + { return this.anyAttrField; } - set { + set + { this.anyAttrField = value; } } } - + /// [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.312")] [DataContractAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://opcfoundation.org/BinarySchema/")] - public partial class FieldType { - + [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://opcfoundation.org/BinarySchema/")] + public partial class FieldType + { + private Documentation documentationField; - + private string nameField; - + private System.Xml.XmlQualifiedName typeNameField; - + private uint lengthField; - + private bool lengthFieldSpecified; - + private string lengthFieldField; - + private bool isLengthInBytesField; - + private string switchFieldField; - + private uint switchValueField; - + private bool switchValueFieldSpecified; - + private SwitchOperand switchOperandField; - + private bool switchOperandFieldSpecified; - + private byte[] terminatorField; - + private string[] anyAttrField; - + /// - public FieldType() { + public FieldType() + { this.isLengthInBytesField = false; } - + /// - public Documentation Documentation { - get { + public Documentation Documentation + { + get + { return this.documentationField; } - set { + set + { this.documentationField = value; } } - + /// [System.Xml.Serialization.XmlAttributeAttribute()] - public string Name { - get { + public string Name + { + get + { return this.nameField; } - set { + set + { this.nameField = value; } } - + /// [System.Xml.Serialization.XmlAttributeAttribute()] - public System.Xml.XmlQualifiedName TypeName { - get { + public System.Xml.XmlQualifiedName TypeName + { + get + { return this.typeNameField; } - set { + set + { this.typeNameField = value; } } - + /// [System.Xml.Serialization.XmlAttributeAttribute()] - public uint Length { - get { + public uint Length + { + get + { return this.lengthField; } - set { + set + { this.lengthField = value; } } - + /// [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool LengthSpecified { - get { + public bool LengthSpecified + { + get + { return this.lengthFieldSpecified; } - set { + set + { this.lengthFieldSpecified = value; } } - + /// [System.Xml.Serialization.XmlAttributeAttribute()] - public string LengthField { - get { + public string LengthField + { + get + { return this.lengthFieldField; } - set { + set + { this.lengthFieldField = value; } } - + /// [System.Xml.Serialization.XmlAttributeAttribute()] [System.ComponentModel.DefaultValueAttribute(false)] - public bool IsLengthInBytes { - get { + public bool IsLengthInBytes + { + get + { return this.isLengthInBytesField; } - set { + set + { this.isLengthInBytesField = value; } } - + /// [System.Xml.Serialization.XmlAttributeAttribute()] - public string SwitchField { - get { + public string SwitchField + { + get + { return this.switchFieldField; } - set { + set + { this.switchFieldField = value; } } - + /// [System.Xml.Serialization.XmlAttributeAttribute()] - public uint SwitchValue { - get { + public uint SwitchValue + { + get + { return this.switchValueField; } - set { + set + { this.switchValueField = value; } } - + /// [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool SwitchValueSpecified { - get { + public bool SwitchValueSpecified + { + get + { return this.switchValueFieldSpecified; } - set { + set + { this.switchValueFieldSpecified = value; } } - + /// [System.Xml.Serialization.XmlAttributeAttribute()] - public SwitchOperand SwitchOperand { - get { + public SwitchOperand SwitchOperand + { + get + { return this.switchOperandField; } - set { + set + { this.switchOperandField = value; } } - + /// [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool SwitchOperandSpecified { - get { + public bool SwitchOperandSpecified + { + get + { return this.switchOperandFieldSpecified; } - set { + set + { this.switchOperandFieldSpecified = value; } } - + /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType="hexBinary")] - public byte[] Terminator { - get { + [System.Xml.Serialization.XmlAttributeAttribute(DataType = "hexBinary")] + public byte[] Terminator + { + get + { return this.terminatorField; } - set { + set + { this.terminatorField = value; } } - + /// - public string[] AnyAttr { - get { + public string[] AnyAttr + { + get + { return this.anyAttrField; } - set { + set + { this.anyAttrField = value; } } } - + /// [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.312")] [DataContractAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://opcfoundation.org/BinarySchema/")] - public enum SwitchOperand { - + [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://opcfoundation.org/BinarySchema/")] + public enum SwitchOperand + { + /// Equals, - + /// GreaterThan, - + /// LessThan, - + /// GreaterThanOrEqual, - + /// LessThanOrEqual, - + /// NotEqual, } - + /// [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.312")] [DataContractAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://opcfoundation.org/BinarySchema/")] - public partial class EnumeratedValue { - + [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://opcfoundation.org/BinarySchema/")] + public partial class EnumeratedValue + { + private Documentation documentationField; - + private string nameField; - + private int valueField; - + private bool valueFieldSpecified; - + /// - public Documentation Documentation { - get { + public Documentation Documentation + { + get + { return this.documentationField; } - set { + set + { this.documentationField = value; } } - + /// [System.Xml.Serialization.XmlAttributeAttribute()] - public string Name { - get { + public string Name + { + get + { return this.nameField; } - set { + set + { this.nameField = value; } } - + /// [System.Xml.Serialization.XmlAttributeAttribute()] - public int Value { - get { + public int Value + { + get + { return this.valueField; } - set { + set + { this.valueField = value; } } - + /// [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool ValueSpecified { - get { + public bool ValueSpecified + { + get + { return this.valueFieldSpecified; } - set { + set + { this.valueFieldSpecified = value; } } } - + /// [System.Xml.Serialization.XmlIncludeAttribute(typeof(StructuredType))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(OpaqueType))] @@ -349,299 +417,361 @@ public bool ValueSpecified { [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.312")] [DataContractAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://opcfoundation.org/BinarySchema/")] - public partial class TypeDescription { - + [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://opcfoundation.org/BinarySchema/")] + public partial class TypeDescription + { + private Documentation documentationField; - + private string nameField; - + private ByteOrder defaultByteOrderField; - + private bool defaultByteOrderFieldSpecified; - + /// - public Documentation Documentation { - get { + public Documentation Documentation + { + get + { return this.documentationField; } - set { + set + { this.documentationField = value; } } - + /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType="NCName")] - public string Name { - get { + [System.Xml.Serialization.XmlAttributeAttribute(DataType = "NCName")] + public string Name + { + get + { return this.nameField; } - set { + set + { this.nameField = value; } } - + /// [System.Xml.Serialization.XmlAttributeAttribute()] - public ByteOrder DefaultByteOrder { - get { + public ByteOrder DefaultByteOrder + { + get + { return this.defaultByteOrderField; } - set { + set + { this.defaultByteOrderField = value; } } - + /// [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool DefaultByteOrderSpecified { - get { + public bool DefaultByteOrderSpecified + { + get + { return this.defaultByteOrderFieldSpecified; } - set { + set + { this.defaultByteOrderFieldSpecified = value; } } } - + /// [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.312")] [DataContractAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://opcfoundation.org/BinarySchema/")] - public enum ByteOrder { - + [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://opcfoundation.org/BinarySchema/")] + public enum ByteOrder + { + /// BigEndian, - + /// LittleEndian, } - + /// [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.312")] [DataContractAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://opcfoundation.org/BinarySchema/")] - public partial class StructuredType : TypeDescription { - + [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://opcfoundation.org/BinarySchema/")] + public partial class StructuredType : TypeDescription + { + private FieldType[] fieldField; - + private string[] anyAttrField; - + /// [System.Xml.Serialization.XmlElementAttribute("Field")] - public FieldType[] Field { - get { + public FieldType[] Field + { + get + { return this.fieldField; } - set { + set + { this.fieldField = value; } } - + /// - public string[] AnyAttr { - get { + public string[] AnyAttr + { + get + { return this.anyAttrField; } - set { + set + { this.anyAttrField = value; } } } - + /// [System.Xml.Serialization.XmlIncludeAttribute(typeof(EnumeratedType))] [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.312")] [DataContractAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://opcfoundation.org/BinarySchema/")] - public partial class OpaqueType : TypeDescription { - + [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://opcfoundation.org/BinarySchema/")] + public partial class OpaqueType : TypeDescription + { + private int lengthInBitsField; - + private bool lengthInBitsFieldSpecified; - + private bool byteOrderSignificantField; - + /// - public OpaqueType() { + public OpaqueType() + { this.byteOrderSignificantField = false; } - + /// [System.Xml.Serialization.XmlAttributeAttribute()] - public int LengthInBits { - get { + public int LengthInBits + { + get + { return this.lengthInBitsField; } - set { + set + { this.lengthInBitsField = value; } } - + /// [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool LengthInBitsSpecified { - get { + public bool LengthInBitsSpecified + { + get + { return this.lengthInBitsFieldSpecified; } - set { + set + { this.lengthInBitsFieldSpecified = value; } } - + /// [System.Xml.Serialization.XmlAttributeAttribute()] [System.ComponentModel.DefaultValueAttribute(false)] - public bool ByteOrderSignificant { - get { + public bool ByteOrderSignificant + { + get + { return this.byteOrderSignificantField; } - set { + set + { this.byteOrderSignificantField = value; } } } - + /// [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.312")] [DataContractAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://opcfoundation.org/BinarySchema/")] - public partial class EnumeratedType : OpaqueType { - + [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://opcfoundation.org/BinarySchema/")] + public partial class EnumeratedType : OpaqueType + { + private EnumeratedValue[] enumeratedValueField; - + /// [System.Xml.Serialization.XmlElementAttribute("EnumeratedValue")] - public EnumeratedValue[] EnumeratedValue { - get { + public EnumeratedValue[] EnumeratedValue + { + get + { return this.enumeratedValueField; } - set { + set + { this.enumeratedValueField = value; } } } - + /// [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.312")] [DataContractAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://opcfoundation.org/BinarySchema/")] - public partial class ImportDirective { - + [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://opcfoundation.org/BinarySchema/")] + public partial class ImportDirective + { + private string namespaceField; - + private string locationField; - + /// [System.Xml.Serialization.XmlAttributeAttribute()] - public string Namespace { - get { + public string Namespace + { + get + { return this.namespaceField; } - set { + set + { this.namespaceField = value; } } - + /// [System.Xml.Serialization.XmlAttributeAttribute()] - public string Location { - get { + public string Location + { + get + { return this.locationField; } - set { + set + { this.locationField = value; } } } - + /// [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.312")] [DataContractAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://opcfoundation.org/BinarySchema/")] - [System.Xml.Serialization.XmlRootAttribute(Namespace="http://opcfoundation.org/BinarySchema/", IsNullable=false)] - public partial class TypeDictionary { - + [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://opcfoundation.org/BinarySchema/")] + [System.Xml.Serialization.XmlRootAttribute(Namespace = "http://opcfoundation.org/BinarySchema/", IsNullable = false)] + public partial class TypeDictionary + { + private Documentation documentationField; - + private ImportDirective[] importField; - + private TypeDescription[] itemsField; - + private string targetNamespaceField; - + private ByteOrder defaultByteOrderField; - + private bool defaultByteOrderFieldSpecified; - + /// - public Documentation Documentation { - get { + public Documentation Documentation + { + get + { return this.documentationField; } - set { + set + { this.documentationField = value; } } - + /// [System.Xml.Serialization.XmlElementAttribute("Import")] - public ImportDirective[] Import { - get { + public ImportDirective[] Import + { + get + { return this.importField; } - set { + set + { this.importField = value; } } - + /// [System.Xml.Serialization.XmlElementAttribute("EnumeratedType", typeof(EnumeratedType))] [System.Xml.Serialization.XmlElementAttribute("OpaqueType", typeof(OpaqueType))] [System.Xml.Serialization.XmlElementAttribute("StructuredType", typeof(StructuredType))] - public TypeDescription[] Items { - get { + public TypeDescription[] Items + { + get + { return this.itemsField; } - set { + set + { this.itemsField = value; } } - + /// [System.Xml.Serialization.XmlAttributeAttribute()] - public string TargetNamespace { - get { + public string TargetNamespace + { + get + { return this.targetNamespaceField; } - set { + set + { this.targetNamespaceField = value; } } - + /// [System.Xml.Serialization.XmlAttributeAttribute()] - public ByteOrder DefaultByteOrder { - get { + public ByteOrder DefaultByteOrder + { + get + { return this.defaultByteOrderField; } - set { + set + { this.defaultByteOrderField = value; } } - + /// [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool DefaultByteOrderSpecified { - get { + public bool DefaultByteOrderSpecified + { + get + { return this.defaultByteOrderFieldSpecified; } - set { + set + { this.defaultByteOrderFieldSpecified = value; } } diff --git a/Stack/Opc.Ua.Core/Types/Schemas/XmlSchemaValidator.cs b/Stack/Opc.Ua.Core/Types/Schemas/XmlSchemaValidator.cs index f9c5cacf7..ce3730068 100644 --- a/Stack/Opc.Ua.Core/Types/Schemas/XmlSchemaValidator.cs +++ b/Stack/Opc.Ua.Core/Types/Schemas/XmlSchemaValidator.cs @@ -137,9 +137,7 @@ public override string GetSchema(string typeName) { foreach (XmlSchemaObject current in m_schema.Elements.Values) { - XmlSchemaElement element = current as XmlSchemaElement; - - if (element != null) + if (current is XmlSchemaElement element) { if (element.Name == typeName) { @@ -159,7 +157,7 @@ public override string GetSchema(string typeName) writer.Dispose(); } - return new UTF8Encoding().GetString(ostrm.ToArray()); + return Encoding.UTF8.GetString(ostrm.ToArray()); } #endregion diff --git a/Stack/Opc.Ua.Core/Types/Utils/AsyncAutoResetEvent.cs b/Stack/Opc.Ua.Core/Types/Utils/AsyncAutoResetEvent.cs index a59fa3b62..62d7ba85b 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/AsyncAutoResetEvent.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/AsyncAutoResetEvent.cs @@ -57,6 +57,9 @@ public Task WaitAsync() } else { + // TaskCreationOptions.RunContinuationsAsynchronously is not necessary + // unless the setting thread shares locks with the waiting thread. + // Then dead locks may occur an the option is required. var tcs = new TaskCompletionSource(); m_waits.Enqueue(tcs); return tcs.Task; diff --git a/Stack/Opc.Ua.Core/Types/Utils/DataComparer.cs b/Stack/Opc.Ua.Core/Types/Utils/DataComparer.cs index e0b8bb234..4593d981f 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/DataComparer.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/DataComparer.cs @@ -935,9 +935,8 @@ public static object GetExtensionObjectBody(ExtensionObject value) { object body = value.Body; - IEncodeable encodeable = body as IEncodeable; - if (encodeable != null) + if (body is IEncodeable encodeable) { return encodeable; } @@ -953,9 +952,8 @@ public static object GetExtensionObjectBody(ExtensionObject value) Factory = EncodeableFactory }; - XmlElement xml = body as XmlElement; - if (xml != null) + if (body is XmlElement xml) { XmlQualifiedName xmlName = Opc.Ua.EncodeableFactory.GetXmlName(expectedType); XmlDecoder decoder = new XmlDecoder(xml, context); @@ -968,9 +966,8 @@ public static object GetExtensionObjectBody(ExtensionObject value) return (IEncodeable)body; } - byte[] bytes = body as byte[]; - if (bytes != null) + if (body is byte[] bytes) { BinaryDecoder decoder = new BinaryDecoder(bytes, context); body = decoder.ReadEncodeable(null, expectedType); @@ -1009,10 +1006,7 @@ public bool CompareExtensionObject(ExtensionObject value1, ExtensionObject value return body1 == body2; } - byte[] bytes1 = value1.Body as byte[]; - byte[] bytes2 = value2.Body as byte[]; - - if (bytes1 != null && bytes2 != null) + if (value1.Body is byte[] bytes1 && value2.Body is byte[] bytes2) { if (!CompareExpandedNodeId(value1.TypeId, value2.TypeId)) { @@ -1022,10 +1016,7 @@ public bool CompareExtensionObject(ExtensionObject value1, ExtensionObject value return CompareByteString(bytes1, bytes2); } - XmlElement xml1 = value1.Body as XmlElement; - XmlElement xml2 = value2.Body as XmlElement; - - if (xml1 != null && xml2 != null) + if (value1.Body is XmlElement xml1 && value2.Body is XmlElement xml2) { if (!CompareExpandedNodeId(value1.TypeId, value2.TypeId)) { @@ -1056,10 +1047,7 @@ protected virtual bool CompareExtensionObjectBody(object value1, object value2) return true; } - IEncodeable encodeable1 = value1 as IEncodeable; - IEncodeable encodeable2 = value2 as IEncodeable; - - if (encodeable1 != null && encodeable2 != null) + if (value1 is IEncodeable encodeable1 && value2 is IEncodeable encodeable2) { if (encodeable1.IsEqual(encodeable2)) { diff --git a/Stack/Opc.Ua.Core/Types/Utils/DataGenerator.cs b/Stack/Opc.Ua.Core/Types/Utils/DataGenerator.cs index f891df5e2..5c38999ec 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/DataGenerator.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/DataGenerator.cs @@ -550,7 +550,7 @@ public T[] GetNullArray(int length, bool fixedLength) for (int ii = 0; ii < value.Length; ii++) { - value[ii] = default(T); + value[ii] = default; } return value; diff --git a/Stack/Opc.Ua.Core/Types/Utils/LoggerUtils.cs b/Stack/Opc.Ua.Core/Types/Utils/LoggerUtils.cs index e49f3e59a..a868025f9 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/LoggerUtils.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/LoggerUtils.cs @@ -26,7 +26,7 @@ * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ - + // // Portions of this logging abstraction class were derived from: // @@ -37,7 +37,7 @@ // // Disable: 'Use the LoggerMessage delegates' -#pragma warning disable CA1848 +#pragma warning disable CA1848 using System; using System.Diagnostics; diff --git a/Stack/Opc.Ua.Core/Types/Utils/NamespaceTable.cs b/Stack/Opc.Ua.Core/Types/Utils/NamespaceTable.cs index 3cd970662..40d37b80a 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/NamespaceTable.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/NamespaceTable.cs @@ -259,7 +259,7 @@ public ushort[] CreateMapping(StringTable source, bool updateTable) #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private List m_strings; #if DEBUG diff --git a/Stack/Opc.Ua.Core/Types/Utils/NumericRange.cs b/Stack/Opc.Ua.Core/Types/Utils/NumericRange.cs index bb37c6e69..ef714ae25 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/NumericRange.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/NumericRange.cs @@ -200,18 +200,16 @@ public bool EnsureValid(object value) int count = -1; // check for collections. - ICollection collection = value as ICollection; - if (collection != null) + if (value is ICollection collection) { count = collection.Count; } else { // check for arrays. - Array array = value as Array; - if (array != null) + if (value is Array array) { count = array.Length; } @@ -492,9 +490,7 @@ private StatusCode ApplyMultiRange(ref object value) // check for matrix. if (array == null) { - Matrix matrix = value as Matrix; - - if (matrix == null || matrix.Dimensions.Length != m_subranges.Length) + if (!(value is Matrix matrix) || matrix.Dimensions.Length != m_subranges.Length) { value = null; return StatusCodes.BadIndexRangeNoData; @@ -636,10 +632,9 @@ public StatusCode UpdateRange(ref object dst, object src) // check for subset of string. if (dstTypeInfo.BuiltInType == BuiltInType.String) { - string srcString = src as string; char[] dstString = ((string)dst).ToCharArray(); - if (srcString == null || srcString.Length != this.Count) + if (!(src is string srcString) || srcString.Length != this.Count) { return StatusCodes.BadIndexRangeInvalid; } @@ -661,10 +656,9 @@ public StatusCode UpdateRange(ref object dst, object src) // update elements within a byte string. else if (dstTypeInfo.BuiltInType == BuiltInType.ByteString) { - byte[] srcString = src as byte[]; byte[] dstString = (byte[])dst; - if (srcString == null || srcString.Length != this.Count) + if (!(src is byte[] srcString) || srcString.Length != this.Count) { return StatusCodes.BadIndexRangeInvalid; } @@ -692,9 +686,7 @@ public StatusCode UpdateRange(ref object dst, object src) // check for destinations specified as a matrix. if (dstArray == null) { - Matrix matrix = dst as Matrix; - - if (matrix == null || m_subranges == null || matrix.Dimensions.Length != m_subranges.Length) + if (!(dst is Matrix matrix) || m_subranges == null || matrix.Dimensions.Length != m_subranges.Length) { return StatusCodes.BadIndexRangeInvalid; } @@ -705,9 +697,7 @@ public StatusCode UpdateRange(ref object dst, object src) // check for input specified as a matrix. if (srcArray == null) { - Matrix matrix = src as Matrix; - - if (matrix == null || m_subranges == null || matrix.Dimensions.Length != m_subranges.Length) + if (!(src is Matrix matrix) || m_subranges == null || matrix.Dimensions.Length != m_subranges.Length) { return StatusCodes.BadIndexRangeInvalid; } @@ -987,9 +977,8 @@ public StatusCode ApplyRange(ref object value) if (array == null && list == null) { // check for string. - String chars = value as String; - if (chars != null) + if (value is String chars) { isString = true; array = chars.ToCharArray(); diff --git a/Stack/Opc.Ua.Core/Types/Utils/ServiceMessageContext.cs b/Stack/Opc.Ua.Core/Types/Utils/ServiceMessageContext.cs index ea71202d3..06136e6d3 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/ServiceMessageContext.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/ServiceMessageContext.cs @@ -198,7 +198,7 @@ public IEncodeableFactory Factory #endregion #region Private Fields - private object m_lock = new object(); + private readonly object m_lock = new object(); private int m_maxStringLength; private int m_maxByteStringLength; private int m_maxArrayLength; diff --git a/Stack/Opc.Ua.Core/Types/Utils/ServiceResult.cs b/Stack/Opc.Ua.Core/Types/Utils/ServiceResult.cs index fe03fa187..970d65844 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/ServiceResult.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/ServiceResult.cs @@ -267,9 +267,7 @@ public ServiceResult( string defaultNamespaceUri, LocalizedText defaultLocalizedText) { - ServiceResultException sre = e as ServiceResultException; - - if (sre != null) + if (e is ServiceResultException sre) { m_code = sre.StatusCode; m_namespaceUri = sre.NamespaceUri; @@ -430,9 +428,8 @@ public static ServiceResult Create(uint code, TranslationInfo translation) public static ServiceResult Create(Exception e, TranslationInfo translation, uint defaultCode) { // replace the default code with the one from the exception. - ServiceResultException sre = e as ServiceResultException; - if (sre != null) + if (e is ServiceResultException sre) { defaultCode = sre.StatusCode; } @@ -469,9 +466,8 @@ public static ServiceResult Create(uint code, string format, params object[] arg public static ServiceResult Create(Exception e, uint defaultCode, string format, params object[] args) { // replace the default code with the one from the exception. - ServiceResultException sre = e as ServiceResultException; - if (sre != null) + if (e is ServiceResultException sre) { defaultCode = sre.StatusCode; } diff --git a/Stack/Opc.Ua.Core/Types/Utils/ServiceResultException.cs b/Stack/Opc.Ua.Core/Types/Utils/ServiceResultException.cs index 5202419e9..4ddba641e 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/ServiceResultException.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/ServiceResultException.cs @@ -21,6 +21,7 @@ namespace Opc.Ua /// An exception thrown when a UA defined error occurs. /// [DataContractAttribute] + [SerializableAttribute] public class ServiceResultException : Exception { #region Constructors @@ -112,12 +113,12 @@ public ServiceResultException(ServiceResult status) : base(GetMessage(status)) /// /// The namespace that qualifies symbolic identifier. - /// + /// public string NamespaceUri => m_status.NamespaceUri; /// /// The qualified name of the symbolic identifier associated with the status code. - /// + /// public string SymbolicId => m_status.SymbolicId; /// diff --git a/Stack/Opc.Ua.Core/Types/Utils/Tracing.cs b/Stack/Opc.Ua.Core/Types/Utils/Tracing.cs index 92bc96733..3a8e58526 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/Tracing.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/Tracing.cs @@ -20,7 +20,7 @@ namespace Opc.Ua public class Tracing { #region Private Members - private static object s_syncRoot = new object(); + private readonly static object s_syncRoot = new object(); private static Tracing s_instance; #endregion Private Members diff --git a/Stack/Opc.Ua.Core/Types/Utils/TypeInfo.cs b/Stack/Opc.Ua.Core/Types/Utils/TypeInfo.cs index 690c06359..93d5645ea 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/TypeInfo.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/TypeInfo.cs @@ -12,6 +12,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System; using System.Reflection; +using System.Threading; +using System.Threading.Tasks; using System.Xml; namespace Opc.Ua @@ -225,9 +227,7 @@ public static NodeId GetDataTypeId(object value) if (dataTypeId == Opc.Ua.NodeId.Null) { - Matrix matrix = value as Matrix; - - if (matrix != null) + if (value is Matrix matrix) { return GetDataTypeId(matrix.TypeInfo); } @@ -317,9 +317,7 @@ public static int GetValueRank(object value) if (typeInfo.BuiltInType == BuiltInType.Null) { - Matrix matrix = value as Matrix; - - if (matrix != null) + if (value is Matrix matrix) { return matrix.TypeInfo.ValueRank; } @@ -512,6 +510,44 @@ public static BuiltInType GetBuiltInType(NodeId datatypeId, ITypeTable typeTree) return BuiltInType.Null; } +#if (NET_STANDARD_ASYNC) + /// + /// Returns the BuiltInType type for the DataTypeId. + /// + /// The data type identyfier for a node in a server's address space.. + /// The type tree for a server. . + /// + /// + /// A value for + /// + public static async Task GetBuiltInTypeAsync(NodeId datatypeId, ITypeTable typeTree, CancellationToken ct = default) + { + NodeId typeId = datatypeId; + + while (!Opc.Ua.NodeId.IsNull(typeId)) + { + if (typeId != null && typeId.NamespaceIndex == 0 && typeId.IdType == Opc.Ua.IdType.Numeric) + { + BuiltInType id = (BuiltInType)(int)(uint)typeId.Identifier; + + if (id > BuiltInType.Null && id <= BuiltInType.Enumeration && id != BuiltInType.DiagnosticInfo) + { + return id; + } + } + + if (typeTree == null) + { + break; + } + + typeId = await typeTree.FindSuperTypeAsync(typeId, ct).ConfigureAwait(false); + } + + return BuiltInType.Null; + } +#endif + /// /// Returns the system type for the datatype. /// @@ -886,12 +922,11 @@ public static TypeInfo IsInstanceOfDataType( return null; } - // check every element in the array or matrix. + // check every element in the array or matrix. Array array = value as Array; if (array == null) { - Matrix matrix = value as Matrix; - if (matrix != null) + if (value is Matrix matrix) { array = matrix.Elements; } @@ -977,14 +1012,12 @@ public NodeId GetDataTypeId(object value, NamespaceTable namespaceUris, ITypeTab if (BuiltInType == BuiltInType.ExtensionObject) { - IEncodeable encodeable = value as IEncodeable; - if (encodeable != null) + if (value is IEncodeable encodeable) { return ExpandedNodeId.ToNodeId(encodeable.TypeId, namespaceUris); } - ExtensionObject extension = value as ExtensionObject; - if (extension != null) + if (value is ExtensionObject extension) { encodeable = extension.Body as IEncodeable; if (encodeable != null) @@ -1138,9 +1171,7 @@ public static TypeInfo Construct(object value) // check for instances of matrices. if (typeInfo.BuiltInType == BuiltInType.Null) { - Matrix matrix = value as Matrix; - - if (matrix != null) + if (value is Matrix matrix) { return matrix.TypeInfo; } @@ -1214,7 +1245,7 @@ public static TypeInfo Construct(Type systemType) return TypeInfo.Unknown; } - // check for generic type. + // check for generic type. if (systemType.GetTypeInfo().IsGenericType) { Type[] argTypes = systemType.GetGenericArguments(); @@ -1310,7 +1341,7 @@ public static TypeInfo Construct(Type systemType) } } - // unknown type. + // unknown type. return TypeInfo.Unknown; } @@ -3085,8 +3116,7 @@ public override bool Equals(object obj) return true; } - TypeInfo typeInfo = obj as TypeInfo; - if (typeInfo != null) + if (obj is TypeInfo typeInfo) { return (m_builtInType == typeInfo.BuiltInType && m_valueRank == typeInfo.ValueRank); diff --git a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs index 91b406d75..46abc7e35 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs @@ -174,7 +174,7 @@ public static bool IsUriHttpsScheme(string url) #endif private static string s_traceFileName = string.Empty; - private static object s_traceFileLock = new object(); + private readonly static object s_traceFileLock = new object(); /// /// The possible trace output mechanisms. @@ -492,9 +492,7 @@ internal static StringBuilder TraceExceptionMessage(Exception e, string format, // append exception information. if (e != null) { - ServiceResultException sre = e as ServiceResultException; - - if (sre != null) + if (e is ServiceResultException sre) { message.AppendFormat(CultureInfo.InvariantCulture, " {0} '{1}'", StatusCodes.GetBrowseName(sre.StatusCode), sre.Message); } @@ -1731,8 +1729,7 @@ public static object Clone(object value) } // copy arrays, any dimension. - Array array = value as Array; - if (array != null) + if (value is Array array) { if (array.Rank == 1) { @@ -1773,15 +1770,13 @@ public static object Clone(object value) } // copy XmlNode. - XmlNode node = value as XmlNode; - if (node != null) + if (value is XmlNode node) { return node.CloneNode(true); } // use ICloneable if supported - ICloneable cloneable = value as ICloneable; - if (cloneable != null) + if (value is ICloneable cloneable) { return cloneable.Clone(); } @@ -1920,17 +1915,15 @@ public static bool IsEqual(object value1, object value2) } // check for compareable objects. - IComparable comparable1 = value1 as IComparable; - if (comparable1 != null) + if (value1 is IComparable comparable1) { return comparable1.CompareTo(value2) == 0; } // check for encodeable objects. - IEncodeable encodeable1 = value1 as IEncodeable; - if (encodeable1 != null) + if (value1 is IEncodeable encodeable1) { if (!(value2 is IEncodeable encodeable2)) { @@ -1941,9 +1934,8 @@ public static bool IsEqual(object value1, object value2) } // check for XmlElement objects. - XmlElement element1 = value1 as XmlElement; - if (element1 != null) + if (value1 is XmlElement element1) { if (!(value2 is XmlElement element2)) { @@ -1954,9 +1946,8 @@ public static bool IsEqual(object value1, object value2) } // check for arrays. - Array array1 = value1 as Array; - if (array1 != null) + if (value1 is Array array1) { // arrays are greater than non-arrays. if (!(value2 is Array array2)) @@ -2008,9 +1999,8 @@ public static bool IsEqual(object value1, object value2) } // check enumerables. - IEnumerable enumerable1 = value1 as IEnumerable; - if (enumerable1 != null) + if (value1 is IEnumerable enumerable1) { // collections are greater than non-collections. if (!(value2 is IEnumerable enumerable2)) @@ -2314,7 +2304,7 @@ public static T ParseExtension(IList extensions, XmlQualifiedName // check if nothing to search for. if (extensions == null || extensions.Count == 0) { - return default(T); + return default; } // use the type name as the default. @@ -2360,7 +2350,7 @@ public static T ParseExtension(IList extensions, XmlQualifiedName } } - return default(T); + return default; } /// @@ -2477,9 +2467,7 @@ public static string GetDataMemberName(PropertyInfo property) { for (int ii = 0; ii < attributes.Length; ii++) { - DataMemberAttribute contract = attributes[ii] as DataMemberAttribute; - - if (contract != null) + if (attributes[ii] is DataMemberAttribute contract) { if (String.IsNullOrEmpty(contract.Name)) { @@ -2631,9 +2619,14 @@ public static byte[] Append(params byte[][] arrays) /// public static X509Certificate2 ParseCertificateBlob(byte[] certificateData) { + // macOS X509Certificate2 constructor throws exception if a certchain is encoded // use AsnParser on macOS to parse for byteblobs, +#if !NETFRAMEWORK bool useAsnParser = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); +#else + bool useAsnParser = false; +#endif try { if (useAsnParser) @@ -2666,7 +2659,11 @@ public static X509Certificate2Collection ParseCertificateChainBlob(byte[] certif List certificatesBytes = new List(certificateData); // macOS X509Certificate2 constructor throws exception if a certchain is encoded // use AsnParser on macOS to parse for byteblobs, +#if !NETFRAMEWORK bool useAsnParser = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); +#else + bool useAsnParser = false; +#endif while (certificatesBytes.Count > 0) { X509Certificate2 certificate; @@ -2834,7 +2831,7 @@ private static byte[] PSHA(HMAC hmac, string label, byte[] data, int offset, int // convert label to UTF-8 byte sequence. if (!String.IsNullOrEmpty(label)) { - seed = new UTF8Encoding().GetBytes(label); + seed = Encoding.UTF8.GetBytes(label); } // append data to label. diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj index e176f9441..47661d118 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj @@ -7,8 +7,8 @@ - - + + all diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs index 56e8a0cde..3bc9532d8 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs @@ -62,6 +62,10 @@ public class TypeSystemClientTest : IUAClient string m_pkiRoot; Uri m_url; + // for test that fetched and browsed node count match + int m_fetchedNodesCount; + int m_browsedNodesCount; + public TypeSystemClientTest() { m_uriScheme = Utils.UriSchemeOpcTcp; @@ -79,6 +83,9 @@ public TypeSystemClientTest(string uriScheme) [OneTimeSetUp] public Task OneTimeSetUp() { + m_fetchedNodesCount = -1; + m_browsedNodesCount = -1; + return OneTimeSetUpAsync(null); } @@ -97,6 +104,7 @@ public async Task OneTimeSetUpAsync(TextWriter writer = null) SecurityNone = true, AutoAccept = true, AllNodeManagers = true, + OperationLimits = true, }; if (writer != null) { @@ -192,46 +200,51 @@ public async Task LoadTypeSystem(bool onlyEnumTypes, bool disableDataTypeDefinit } [Test, Order(200)] - public async Task BrowseComplexTypesServer() + public async Task BrowseComplexTypesServerAsync() { var samples = new ClientSamples(TestContext.Out, null, null, true); - await samples.LoadTypeSystem(Session).ConfigureAwait(false); + await samples.LoadTypeSystemAsync(Session).ConfigureAwait(false); ReferenceDescriptionCollection referenceDescriptions = - samples.BrowseFullAddressSpace(this, Objects.RootFolder); + await samples.BrowseFullAddressSpaceAsync(this, Objects.RootFolder).ConfigureAwait(false); TestContext.Out.WriteLine("References: {0}", referenceDescriptions.Count); + m_browsedNodesCount = referenceDescriptions.Count; NodeIdCollection variableIds = new NodeIdCollection(referenceDescriptions - .Where(r => r.NodeClass == NodeClass.Variable && r.TypeDefinition.NamespaceIndex != 0) + .Where(r => r.NodeClass == NodeClass.Variable) .Select(r => ExpandedNodeId.ToNodeId(r.NodeId, m_session.NamespaceUris))); TestContext.Out.WriteLine("VariableIds: {0}", variableIds.Count); (var values, var serviceResults) = await samples.ReadAllValuesAsync(this, variableIds).ConfigureAwait(false); + int ii = 0; foreach (var serviceResult in serviceResults) { - Assert.IsTrue(ServiceResult.IsGood(serviceResult)); + var result = serviceResults[ii++]; + Assert.IsTrue(ServiceResult.IsGood(serviceResult), $"Expected good result, but received {serviceResult}"); } } [Test, Order(300)] - public async Task FetchComplexTypesServer() + public async Task FetchComplexTypesServerAsync() { var samples = new ClientSamples(TestContext.Out, null, null, true); - await samples.LoadTypeSystem(m_session).ConfigureAwait(false); + await samples.LoadTypeSystemAsync(m_session).ConfigureAwait(false); IList allNodes = null; - allNodes = samples.FetchAllNodesNodeCache( - this, Objects.RootFolder, true, true, false); + allNodes = await samples.FetchAllNodesNodeCacheAsync( + this, Objects.RootFolder, true, false, false).ConfigureAwait(false); TestContext.Out.WriteLine("References: {0}", allNodes.Count); + m_fetchedNodesCount = allNodes.Count; + NodeIdCollection variableIds = new NodeIdCollection(allNodes - .Where(r => r.NodeClass == NodeClass.Variable && ((VariableNode)r).DataType.NamespaceIndex != 0) + .Where(r => r.NodeClass == NodeClass.Variable && r is VariableNode && ((VariableNode)r).DataType.NamespaceIndex != 0) .Select(r => ExpandedNodeId.ToNodeId(r.NodeId, m_session.NamespaceUris))); TestContext.Out.WriteLine("VariableIds: {0}", variableIds.Count); @@ -242,13 +255,99 @@ public async Task FetchComplexTypesServer() { Assert.IsTrue(ServiceResult.IsGood(serviceResult)); } + + // check if complex type is properly decoded + bool testFailed = false; + for (int ii = 0; ii < values.Count; ii++) + { + DataValue value = values[ii]; + NodeId variableId = variableIds[ii]; + ExpandedNodeId variableExpandedNodeId = NodeId.ToExpandedNodeId(variableId, m_session.NamespaceUris); + var variableNode = allNodes.Where(n => n.NodeId == variableId).FirstOrDefault() as VariableNode; + if (variableNode != null && + variableNode.DataType.NamespaceIndex != 0) + { + TestContext.Out.WriteLine("Check for custom type: {0}", variableNode); + ExpandedNodeId fullTypeId = NodeId.ToExpandedNodeId(variableNode.DataType, m_session.NamespaceUris); + Type type = m_session.Factory.GetSystemType(fullTypeId); + if (type == null) + { + // check for opaque type + NodeId superType = m_session.NodeCache.FindSuperType(fullTypeId); + NodeId lastGoodType = variableNode.DataType; + while (!superType.IsNullNodeId && superType != DataTypes.BaseDataType) + { + if (superType == DataTypeIds.Structure) + { + testFailed = true; + break; + } + lastGoodType = superType; + superType = m_session.NodeCache.FindSuperType(superType); + } + + if (testFailed) + { + TestContext.Out.WriteLine("-- Variable: {0} complex type unavailable --> {1}", variableNode.NodeId, variableNode.DataType); + (_, _) = await samples.ReadAllValuesAsync(this, new NodeIdCollection() { variableId }).ConfigureAwait(false); + } + else + { + TestContext.Out.WriteLine("-- Variable: {0} opaque typeid --> {1}", variableNode.NodeId, lastGoodType); + } + continue; + } + + if (value.Value is ExtensionObject) + { + Type valueType = ((ExtensionObject)value.Value).Body.GetType(); + if (valueType != type) + { + testFailed = true; + TestContext.Out.WriteLine("Variable: {0} type is decoded as ExtensionObject --> {1}", variableNode, value.Value); + (_, _) = await samples.ReadAllValuesAsync(this, new NodeIdCollection() { variableId }).ConfigureAwait(false); + } + continue; + } + + if (value.Value is Array array && + array.GetType().GetElementType() == typeof(ExtensionObject)) + { + foreach (ExtensionObject valueItem in array) + { + Type valueType = valueItem.Body.GetType(); + if (valueType != type) + { + testFailed = true; + TestContext.Out.WriteLine("Variable: {0} type is decoded as ExtensionObject --> {1}", variableNode, valueItem); + (_, _) = await samples.ReadAllValuesAsync(this, new NodeIdCollection() { variableId }).ConfigureAwait(false); + } + } + } + } + } + + if (testFailed) + { + Assert.Fail("Test failed, unknown or undecodable complex type detected. See log for information."); + } + } + + [Test, Order(330)] + public void ValidateFetchedAndBrowsedNodesMatch() + { + if (m_browsedNodesCount < 0 || m_fetchedNodesCount < 0) + { + Assert.Ignore("The browse or fetch test did not run."); + } + Assert.AreEqual(m_fetchedNodesCount, m_browsedNodesCount); } [Test, Order(400)] - public async Task ReadWriteScalaVariableType() + public async Task ReadWriteScalarVariableTypeAsync() { var samples = new ClientSamples(TestContext.Out, null, null, true); - await samples.LoadTypeSystem(m_session).ConfigureAwait(false); + await samples.LoadTypeSystemAsync(m_session).ConfigureAwait(false); // test the static version of the structure ExpandedNodeId structureVariable = TestData.VariableIds.Data_Static_Structure_ScalarStructure; diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs index 938ea4324..354ba90c1 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2021 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 @@ -11,7 +11,7 @@ * 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, @@ -30,6 +30,7 @@ using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace Opc.Ua.Client.ComplexTypes.Tests.Types @@ -74,26 +75,26 @@ private void Initialize() public IEncodeableFactory Factory => m_factory; /// - public Task> LoadDataTypeSystem(NodeId dataTypeSystem = null) + public Task> LoadDataTypeSystem(NodeId dataTypeSystem = null, CancellationToken ct = default) { return Task.FromResult(m_dataTypeDictionary); } /// - public IList BrowseForEncodings(IList nodeIds, string[] supportedEncodings) + public Task> BrowseForEncodingsAsync(IList nodeIds, string[] supportedEncodings, CancellationToken ct = default) { - return new List(); + return Task.FromResult((IList)new List()); } /// - public IList BrowseForEncodings( + public Task<(IList encodings, ExpandedNodeId binaryEncodingId, ExpandedNodeId xmlEncodingId)> BrowseForEncodingsAsync( ExpandedNodeId nodeId, string[] supportedEncodings, - out ExpandedNodeId binaryEncodingId, - out ExpandedNodeId xmlEncodingId) + CancellationToken ct = default) { - binaryEncodingId = ExpandedNodeId.Null; - xmlEncodingId = ExpandedNodeId.Null; + var binaryEncodingId = ExpandedNodeId.Null; + var xmlEncodingId = ExpandedNodeId.Null; + IList encodings = null; var node = m_dataTypeNodes[ExpandedNodeId.ToNodeId(nodeId, NamespaceUris)]; if (node is DataTypeNode dataTypeNode) @@ -121,33 +122,26 @@ public IList BrowseForEncodings( } result.Add(ExpandedNodeId.ToNodeId(reference.TargetId, NamespaceUris)); } - return result; + encodings = result; } - return null; + return Task.FromResult((encodings, binaryEncodingId, xmlEncodingId)); } /// - public bool BrowseTypeIdsForDictionaryComponent( + public Task<(ExpandedNodeId typeId, ExpandedNodeId encodingId, DataTypeNode dataTypeNode)> BrowseTypeIdsForDictionaryComponentAsync( ExpandedNodeId nodeId, - out ExpandedNodeId typeId, - out ExpandedNodeId encodingId, - out DataTypeNode dataTypeNode) + CancellationToken ct = default) { - typeId = ExpandedNodeId.Null; - encodingId = ExpandedNodeId.Null; - dataTypeNode = null; - - // not implemented yet - - return false; + return Task.FromResult<(ExpandedNodeId typeId, ExpandedNodeId encodingId, DataTypeNode dataTypeNode)>((null, null, null)); } /// - public IList LoadDataTypes( + public async Task> LoadDataTypesAsync( ExpandedNodeId dataType, bool nestedSubTypes = false, bool addRootNode = false, - bool filterUATypes = true) + bool filterUATypes = true, + CancellationToken ct = default) { var result = new List(); var nodesToBrowse = new ExpandedNodeIdCollection { @@ -156,7 +150,7 @@ public IList LoadDataTypes( if (addRootNode) { - var rootNode = Find(dataType); + var rootNode = await FindAsync(dataType, ct).ConfigureAwait(false); if (!(rootNode is DataTypeNode)) { throw new ServiceResultException("Root Node is not a DataType node."); @@ -203,34 +197,33 @@ public IList LoadDataTypes( } /// - public INode Find(ExpandedNodeId nodeId) + public Task FindAsync(ExpandedNodeId nodeId, CancellationToken ct = default) { - return m_dataTypeNodes[ExpandedNodeId.ToNodeId(nodeId, NamespaceUris)]; + return Task.FromResult(m_dataTypeNodes[ExpandedNodeId.ToNodeId(nodeId, NamespaceUris)]); } - /// - public object GetEnumTypeArray(ExpandedNodeId nodeId) + public Task GetEnumTypeArrayAsync(ExpandedNodeId nodeId, CancellationToken ct = default) { - return null; + return Task.FromResult(null); } /// - public NodeId FindSuperType(NodeId typeId) + public Task FindSuperTypeAsync(NodeId typeId, CancellationToken ct = default) { var node = m_dataTypeNodes[typeId]; if (node is DataTypeNode dataTypeNode) { if (dataTypeNode.DataTypeDefinition.Body is EnumDefinition enumDefinition) { - return DataTypeIds.Enumeration; + return Task.FromResult(DataTypeIds.Enumeration); } else if (dataTypeNode.DataTypeDefinition.Body is StructureDefinition structureDefinition) { - return structureDefinition.BaseDataType; + return Task.FromResult(structureDefinition.BaseDataType); } } - return DataTypeIds.BaseDataType; + return Task.FromResult(DataTypeIds.BaseDataType); } #endregion IComplexTypeResolver diff --git a/Tests/Opc.Ua.Client.Tests/ClientFixture.cs b/Tests/Opc.Ua.Client.Tests/ClientFixture.cs index 417c9d3a8..0d2a275c2 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientFixture.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientFixture.cs @@ -53,7 +53,7 @@ public class ClientFixture : IDisposable public int OperationTimeout { get; set; } = 10000; public int TraceMasks { get; set; } = Utils.TraceMasks.Error | Utils.TraceMasks.StackTrace | Utils.TraceMasks.Security | Utils.TraceMasks.Information; public bool UseTracing { get; set; } = false; - public ISessionFactory SessionFactory => UseTracing ? TraceableSessionFactory.Instance : DefaultSessionFactory.Instance; + public ISessionFactory SessionFactory => UseTracing ? (DefaultSessionFactory)HeaderUpdatingSessionFactory.Instance : (DefaultSessionFactory)TestableSessionFactory.Instance; public ActivityListener ActivityListener { get; private set; } #region Public Methods @@ -78,6 +78,8 @@ public async Task LoadClientConfiguration(string pkiRoot = null, string clientNa .Build( "urn:localhost:opcfoundation.org:" + clientName, "http://opcfoundation.org/UA/" + clientName) + .SetMaxByteStringLength(4 * 1024 * 1024) + .SetMaxArrayLength(1024 * 1024) .AsClient() .SetClientOperationLimits(new OperationLimits { MaxNodesPerBrowse = kDefaultOperationLimits, @@ -319,7 +321,9 @@ public async Task GetEndpoints(Uri url) using (var client = DiscoveryClient.Create(url, endpointConfiguration)) { - return await client.GetEndpointsAsync(null).ConfigureAwait(false); + var result = await client.GetEndpointsAsync(null).ConfigureAwait(false); + await client.CloseAsync().ConfigureAwait(false); + return result; } } diff --git a/Tests/Opc.Ua.Client.Tests/ClientTest.cs b/Tests/Opc.Ua.Client.Tests/ClientTest.cs index 7e9986215..09e4a22b1 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTest.cs @@ -29,16 +29,21 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Runtime.Serialization; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; +using Moq; +using Newtonsoft.Json.Linq; using NUnit.Framework; using Opc.Ua.Configuration; using Opc.Ua.Server.Tests; +using static Opc.Ua.Client.HeaderUpdatingSession; namespace Opc.Ua.Client.Tests { @@ -107,6 +112,24 @@ public ClientTest(string uriScheme = Utils.UriSchemeOpcTcp) : } #endregion + // Test class for testing protected methods in HeaderUpdatingSession + public class TestableHeaderUpdatingSession : HeaderUpdatingSession + { + public TestableHeaderUpdatingSession( + ISessionChannel channel, + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint) + : base(channel, configuration, endpoint) + { + } + + // Expose the protected method for testing + public void TestableUpdateRequestHeader(IServiceRequest request, bool useDefaults) + { + base.UpdateRequestHeader(request, useDefaults); + } + } + #region Benchmark Setup /// /// Global Setup for benchmarks. @@ -136,7 +159,10 @@ public async Task GetEndpointsAsync() using (var client = DiscoveryClient.Create(ServerUrl, endpointConfiguration)) { - Endpoints = await client.GetEndpointsAsync(null).ConfigureAwait(false); + Endpoints = await client.GetEndpointsAsync(null, CancellationToken.None).ConfigureAwait(false); + var statusCode = await client.CloseAsync(CancellationToken.None).ConfigureAwait(false); + Assert.AreEqual((StatusCode)StatusCodes.Good, statusCode); + TestContext.Out.WriteLine("Endpoints:"); foreach (var endpoint in Endpoints) { @@ -177,6 +203,9 @@ public async Task FindServersAsync() using (var client = DiscoveryClient.Create(ServerUrl, endpointConfiguration)) { var servers = await client.FindServersAsync(null).ConfigureAwait(false); + var statusCode = await client.CloseAsync(CancellationToken.None).ConfigureAwait(false); + Assert.AreEqual((StatusCode)StatusCodes.Good, statusCode); + foreach (var server in servers) { TestContext.Out.WriteLine("{0}", server.ApplicationName); @@ -202,6 +231,9 @@ public async Task FindServersOnNetworkAsync() try { var response = await client.FindServersOnNetworkAsync(null, 0, 100, null, CancellationToken.None).ConfigureAwait(false); + var statusCode = await client.CloseAsync(CancellationToken.None).ConfigureAwait(false); + Assert.AreEqual((StatusCode)StatusCodes.Good, statusCode); + foreach (ServerOnNetwork server in response.Servers) { TestContext.Out.WriteLine("{0}", server.ServerName); @@ -336,11 +368,35 @@ public async Task ConnectAndCloseAsync(string securityPolicy) var session = await ClientFixture.ConnectAsync(ServerUrl, securityPolicy, Endpoints).ConfigureAwait(false); Assert.NotNull(session); Session.SessionClosing += Session_Closing; - var result = await session.CloseAsync(5_000, closeChannel).ConfigureAwait(false); + var result = await session.CloseAsync(5_000, closeChannel, CancellationToken.None).ConfigureAwait(false); Assert.NotNull(result); session.Dispose(); } + [Test, Order(202)] + public async Task ConnectAndCloseAsyncReadAfterClose() + { + var securityPolicy = SecurityPolicies.Basic256Sha256; + using (var session = await ClientFixture.ConnectAsync(ServerUrl, securityPolicy, Endpoints).ConfigureAwait(false)) + { + Assert.NotNull(session); + Session.SessionClosing += Session_Closing; + + var nodeId = new NodeId(Opc.Ua.VariableIds.ServerStatusType_BuildInfo); + var node = await session.ReadNodeAsync(nodeId, CancellationToken.None).ConfigureAwait(false); + var value = await session.ReadValueAsync(nodeId, CancellationToken.None).ConfigureAwait(false); + + // keep channel open + var result = await session.CloseAsync(1_000, false).ConfigureAwait(false); + Assert.AreEqual((StatusCode)StatusCodes.Good, result); + + await Task.Delay(5_000).ConfigureAwait(false); + + var sre = Assert.ThrowsAsync(async () => await session.ReadNodeAsync(nodeId, CancellationToken.None).ConfigureAwait(false)); + Assert.AreEqual((StatusCode)StatusCodes.BadSessionIdInvalid, sre.StatusCode); + } + } + [Theory, Order(210)] public async Task ConnectAndReconnectAsync(bool reconnectAbort, bool useMaxReconnectPeriod) { @@ -1164,7 +1220,7 @@ public async Task LoadStandardDataTypeSystem() [Test, Order(710)] [TestCaseSource(nameof(TypeSystems))] - public async Task LoadAllServerDataTypeSystems(NodeId dataTypeSystem) + public void LoadAllServerDataTypeSystems(NodeId dataTypeSystem) { // find the dictionary for the description. Browser browser = new Browser(Session) { @@ -1185,7 +1241,7 @@ public async Task LoadAllServerDataTypeSystems(NodeId dataTypeSystem) NodeId dictionaryId = ExpandedNodeId.ToNodeId(r.NodeId, Session.NamespaceUris); TestContext.Out.WriteLine(" ReadDictionary {0} {1}", r.BrowseName.Name, dictionaryId); var dictionaryToLoad = new DataDictionary(Session); - await dictionaryToLoad.Load(dictionaryId, r.BrowseName.Name).ConfigureAwait(false); + dictionaryToLoad.Load(dictionaryId, r.BrowseName.Name); // internal API for testing only var dictionary = dictionaryToLoad.ReadDictionary(dictionaryId); @@ -1195,7 +1251,7 @@ public async Task LoadAllServerDataTypeSystems(NodeId dataTypeSystem) { try { - await dictionaryToLoad.Validate(dictionary, true).ConfigureAwait(false); + dictionaryToLoad.Validate(dictionary, true); } catch (Exception ex) { @@ -1204,7 +1260,7 @@ public async Task LoadAllServerDataTypeSystems(NodeId dataTypeSystem) } else { - await dictionaryToLoad.Validate(dictionary, true).ConfigureAwait(false); + dictionaryToLoad.Validate(dictionary, true); } } } @@ -1262,6 +1318,57 @@ public async Task TransferSubscriptionNative(bool sendInitialData) } } + [Test, Order(900)] + public async Task TestTraceContextIsPropagated() + { + var rootActivity = new Activity("Test_Activity_Root") { + ActivityTraceFlags = ActivityTraceFlags.Recorded, + }.Start(); + + var activityListener = new ActivityListener { + ShouldListenTo = s => true, + Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllDataAndRecorded, + }; + + ActivitySource.AddActivityListener(activityListener); + + using (var activity = new ActivitySource("TestActivitySource").StartActivity("Test_Activity")) + { + if (activity != null && activity.Id != null) + { + var endpoint = await ClientFixture.GetEndpointAsync(ServerUrl, SecurityPolicies.Basic256Sha256, Endpoints).ConfigureAwait(false); + Assert.NotNull(endpoint); + + // Mock the channel and session + var channelMock = new Mock(); + var sessionChannelMock = channelMock.As(); + + TestableHeaderUpdatingSession testableHeaderUpdatingSession = new TestableHeaderUpdatingSession(sessionChannelMock.Object, ClientFixture.Config, endpoint); + CreateSessionRequest request = new CreateSessionRequest(); + request.RequestHeader = new RequestHeader(); + + // Mock call TestableUpdateRequestHeader() to simulate the header update + testableHeaderUpdatingSession.TestableUpdateRequestHeader(request, true); + + // Get the AdditionalHeader from the request + var additionalHeader = request.RequestHeader.AdditionalHeader as ExtensionObject; + Assert.NotNull(additionalHeader); + + // Simulate extraction + var extractedContext = HeaderUpdatingSession.ExtractTraceContextFromParameters(additionalHeader.Body as AdditionalParametersType); + + // Verify that the trace context is propagated. + Assert.AreEqual(activity.TraceId, extractedContext.ActivityContext.TraceId); + Assert.AreEqual(activity.SpanId, extractedContext.ActivityContext.SpanId); + + TestContext.Out.WriteLine($"Activity TraceId: {activity.TraceId}, Activity SpanId: {activity.SpanId}"); + TestContext.Out.WriteLine($"Extracted TraceId: {extractedContext.ActivityContext.TraceId}, Extracted SpanId: {extractedContext.ActivityContext.SpanId}"); + } + } + + rootActivity.Stop(); + } + /// /// Read BuildInfo and ensure the values in the structure are the same as in the properties. /// diff --git a/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs b/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs index cc33e6ff1..8447f5328 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs @@ -59,6 +59,7 @@ public class ClientTestFramework public TokenValidatorMock TokenValidator { get; set; } = new TokenValidatorMock(); public bool SingleSession { get; set; } = true; + public bool UseTracing { get; set; } = true; public bool SupportsExternalServerUrl { get; set; } = false; public ServerFixture ServerFixture { get; set; } public ClientFixture ClientFixture { get; set; } @@ -157,8 +158,11 @@ public async Task OneTimeSetUpAsync(TextWriter writer = null, bool securityNone } ClientFixture = new ClientFixture(); - ClientFixture.UseTracing = true; - ClientFixture.StartActivityListener(); + ClientFixture.UseTracing = UseTracing; + if (UseTracing) + { + ClientFixture.StartActivityListener(); + } await ClientFixture.LoadClientConfiguration(PkiRoot).ConfigureAwait(false); ClientFixture.Config.TransportQuotas.MaxMessageSize = TransportQuotaMaxMessageSize; diff --git a/Tests/Opc.Ua.Client.Tests/HeaderUpdatingSession.cs b/Tests/Opc.Ua.Client.Tests/HeaderUpdatingSession.cs new file mode 100644 index 000000000..a930aebac --- /dev/null +++ b/Tests/Opc.Ua.Client.Tests/HeaderUpdatingSession.cs @@ -0,0 +1,252 @@ +/* ======================================================================== + * Copyright (c) 2005-2023 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; + +namespace Opc.Ua.Client +{ + + /// + /// A subclass of a session to override some implementations from CleintBase + /// + public class HeaderUpdatingSession : Session + { + #region Constructors + /// + /// Constructs a new instance of the class. + /// + /// The channel used to communicate with the server. + /// The configuration for the client application. + /// The endpoint use to initialize the channel. + public HeaderUpdatingSession( + ISessionChannel channel, + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint) + : + this(channel as ITransportChannel, configuration, endpoint, null) + { + } + + /// + /// Constructs a new instance of the class. + /// + /// The channel used to communicate with the server. + /// The configuration for the client application. + /// The endpoint used to initialize the channel. + /// The certificate to use for the client. + /// The list of available endpoints returned by server in GetEndpoints() response. + /// The value of profileUris used in GetEndpoints() request. + /// + /// The application configuration is used to look up the certificate if none is provided. + /// The clientCertificate must have the private key. This will require that the certificate + /// be loaded from a certicate store. Converting a DER encoded blob to a X509Certificate2 + /// will not include a private key. + /// The availableEndpoints and discoveryProfileUris parameters are used to validate + /// that the list of EndpointDescriptions returned at GetEndpoints matches the list returned at CreateSession. + /// + public HeaderUpdatingSession( + ITransportChannel channel, + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint, + X509Certificate2 clientCertificate, + EndpointDescriptionCollection availableEndpoints = null, + StringCollection discoveryProfileUris = null) + : base(channel, configuration, endpoint, clientCertificate, availableEndpoints, discoveryProfileUris) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The channel. + /// The template session. + /// if set to true the event handlers are copied. + public HeaderUpdatingSession(ITransportChannel channel, Session template, bool copyEventHandlers) + : + base(channel, template, copyEventHandlers) + { + } + #endregion + + /// + /// Holds the tracing context for propagation across system boundaries. + /// + public struct TraceContext + { + /// + /// Gets the core trace identifiers like TraceId and SpanId. + /// + public ActivityContext ActivityContext { get; } + + /// + /// Gets the user-defined data associated with the trace. + /// + public Dictionary Baggage { get; } + + /// + /// Constructs a new TraceContext. + /// + /// Core trace identifiers. + /// User-defined data for the trace. + public TraceContext(ActivityContext activityContext, Dictionary baggage) + { + ActivityContext = activityContext; + Baggage = baggage; + } + } + + /// + /// Populates AdditionalParameters with details from the TraceContext + /// + public static void InjectTraceIntoAdditionalParameters(TraceContext context, out AdditionalParametersType traceData) + { + traceData = new AdditionalParametersType(); + + // Determine the trace flag based on the 'Recorded' status. + string traceFlags = (context.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0 ? "01" : "00"; + + // Construct the traceparent header, adhering to the W3C Trace Context format. + string traceparent = $"00-{context.ActivityContext.TraceId}-{context.ActivityContext.SpanId}-{traceFlags}"; + traceData.Parameters.Add(new KeyValuePair() { Key = "traceparent", Value = traceparent }); + + // If baggage (tracestate) exists, include it as a single concatenated string. + if (context.Baggage != null && context.Baggage.Count > 0) + { + var tracestate = string.Join(",", context.Baggage.Select(kv => $"{kv.Key}={kv.Value}")); + traceData.Parameters.Add(new KeyValuePair() { Key = "tracestate", Value = tracestate }); + } + } + + /// + /// Extracts the trace and baggage details from the given dictionary + /// + public static TraceContext ExtractTraceContextFromParameters(AdditionalParametersType parameters) + { + if (parameters == null) + { + throw new ServiceResultException(StatusCodes.BadInvalidArgument, "Parameters are null"); + } + + ActivityTraceId traceId = default; + ActivitySpanId spanId = default; + ActivityTraceFlags traceFlags = ActivityTraceFlags.None; + Dictionary baggage = new Dictionary(); + + foreach (var item in parameters.Parameters) + { + if (item.Key == "traceparent") + { + var parts = item.Value.ToString().Split('-'); + if (parts.Length != 4) + { + throw new ServiceResultException(StatusCodes.BadDecodingError, "Invalid traceparent format"); + } + traceId = ActivityTraceId.CreateFromString(parts[1].AsSpan()); + spanId = ActivitySpanId.CreateFromString(parts[2].AsSpan()); + traceFlags = parts[3] == "01" ? ActivityTraceFlags.Recorded : ActivityTraceFlags.None; + } + else if (item.Key == "tracestate") + { + var baggageItems = item.Value.ToString().Split(','); + foreach (var baggageItem in baggageItems) + { + var keyValue = baggageItem.Split('='); + if (keyValue.Length == 2) + { + baggage[keyValue[0].Trim()] = keyValue[1].Trim(); + } + } + } + } + + if (traceId == default || spanId == default) + { + throw new ServiceResultException(StatusCodes.BadInvalidArgument, "Failed to extract trace context"); + } + + var activityContext = new ActivityContext(traceId, spanId, traceFlags); + return new TraceContext(activityContext, baggage); + } + + /// + [Obsolete("Must override the version with useDefault parameter.")] + protected override void UpdateRequestHeader(IServiceRequest request) + { + UpdateRequestHeader(request, request == null); + } + + /// + protected override void UpdateRequestHeader(IServiceRequest request, bool useDefaults) + { + base.UpdateRequestHeader(request, useDefaults); + + if (Activity.Current != null) + { + InjectTraceIntoAdditionalParameters(new TraceContext(Activity.Current.Context, null), out AdditionalParametersType traceData); + + if (request.RequestHeader.AdditionalHeader == null) + { + request.RequestHeader.AdditionalHeader = new ExtensionObject(traceData); + } + else if (request.RequestHeader.AdditionalHeader.Body is AdditionalParametersType existingParameters) + { + // Merge the trace data into the existing parameters. + existingParameters.Parameters.AddRange(traceData.Parameters); + } + } + } + + /// + protected override void UpdateRequestHeader(IServiceRequest request, bool useDefaults, string serviceName) + { + base.UpdateRequestHeader(request, useDefaults, serviceName); + + if (Activity.Current != null) + { + InjectTraceIntoAdditionalParameters(new TraceContext(Activity.Current.Context, null), out AdditionalParametersType traceData); + + if (request.RequestHeader.AdditionalHeader == null) + { + request.RequestHeader.AdditionalHeader = new ExtensionObject(traceData); + } + else if (request.RequestHeader.AdditionalHeader.Body is AdditionalParametersType existingParameters) + { + // Merge the trace data into the existing parameters. + existingParameters.Parameters.AddRange(traceData.Parameters); + } + } + } + } +} diff --git a/Tests/Opc.Ua.Client.Tests/HeaderUpdatingSessionFactory.cs b/Tests/Opc.Ua.Client.Tests/HeaderUpdatingSessionFactory.cs new file mode 100644 index 000000000..7514626ab --- /dev/null +++ b/Tests/Opc.Ua.Client.Tests/HeaderUpdatingSessionFactory.cs @@ -0,0 +1,54 @@ +/* ======================================================================== + * Copyright (c) 2005-2023 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.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; + +namespace Opc.Ua.Client +{ + /// + /// Object that creates instances of an Opc.Ua.Client.Session object. + /// + public class HeaderUpdatingSessionFactory : TraceableSessionFactory + { + /// + /// The default instance of the factory. + /// + public new static readonly HeaderUpdatingSessionFactory Instance = new HeaderUpdatingSessionFactory(); + + /// + /// Force use of the default instance. + /// + protected HeaderUpdatingSessionFactory() + { + } + } +} diff --git a/Tests/Opc.Ua.Client.Tests/HeaderUpdatingSessionInstantiator.cs b/Tests/Opc.Ua.Client.Tests/HeaderUpdatingSessionInstantiator.cs new file mode 100644 index 000000000..23dcad745 --- /dev/null +++ b/Tests/Opc.Ua.Client.Tests/HeaderUpdatingSessionInstantiator.cs @@ -0,0 +1,71 @@ +/* ======================================================================== + * Copyright (c) 2005-2023 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.Security.Cryptography.X509Certificates; + +namespace Opc.Ua.Client +{ + /// + /// Object that creates an instance of a Session object. + /// It can be used to create instances of enhanced Session + /// classes with added functionality or overridden methods. + /// + public class HeaderUpdatingSessionInstantiator : TraceableSessionFactory + { + #region ISessionInstantiator Members + + /// + /// Object that creates instances of an Opc.Ua.Client.Session object with Activity Source. + /// + public new static readonly HeaderUpdatingSessionInstantiator Instance = new HeaderUpdatingSessionInstantiator(); + + /// + public override Session Create( + ISessionChannel channel, + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint) + { + return new HeaderUpdatingSession(channel, configuration, endpoint); + } + + /// + public override Session Create( + ITransportChannel channel, + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint, + X509Certificate2 clientCertificate, + EndpointDescriptionCollection availableEndpoints = null, + StringCollection discoveryProfileUris = null) + { + return new HeaderUpdatingSession(channel, configuration, endpoint, clientCertificate, availableEndpoints, discoveryProfileUris); + } + + #endregion + } +} diff --git a/Tests/Opc.Ua.Client.Tests/NodeCacheAsyncTest.cs b/Tests/Opc.Ua.Client.Tests/NodeCacheAsyncTest.cs new file mode 100644 index 000000000..bfe0dc13d --- /dev/null +++ b/Tests/Opc.Ua.Client.Tests/NodeCacheAsyncTest.cs @@ -0,0 +1,507 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 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.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using NUnit.Framework; +using Opc.Ua.Server.Tests; + +namespace Opc.Ua.Client.Tests +{ + /// + /// Client tests. + /// + [TestFixture, Category("Client"), Category("NodeCacheAsync")] + [SetCulture("en-us"), SetUICulture("en-us")] + [TestFixtureSource(nameof(FixtureArgs))] + [MemoryDiagnoser] + [DisassemblyDiagnoser] + public class NodeCacheAsyncTest : ClientTestFramework + { + private const int kTestSetSize = 100; + + public NodeCacheAsyncTest(string uriScheme = Utils.UriSchemeOpcTcp) : + base(uriScheme) + { + } + + #region Test Setup + /// + /// Set up a Server and a Client instance. + /// + [OneTimeSetUp] + public new Task OneTimeSetUp() + { + SupportsExternalServerUrl = true; + // create a new session for every test + SingleSession = false; + return base.OneTimeSetUp(); + } + + /// + /// Tear down the Server and the Client. + /// + [OneTimeTearDown] + public new Task OneTimeTearDownAsync() + { + return base.OneTimeTearDownAsync(); + } + + /// + /// Test setup. + /// + [SetUp] + public new async Task SetUp() + { + await base.SetUp().ConfigureAwait(false); + + // clear node cache + Session.NodeCache.Clear(); + } + + /// + /// Test teardown. + /// + [TearDown] + public new Task TearDown() + { + return base.TearDown(); + } + #endregion + + #region Benchmark Setup + /// + /// Global Setup for benchmarks. + /// + [GlobalSetup] + public new void GlobalSetup() + { + base.GlobalSetup(); + } + + /// + /// Global cleanup for benchmarks. + /// + [GlobalCleanup] + public new void GlobalCleanup() + { + base.GlobalCleanup(); + } + #endregion + + #region Test Methods + /// + /// Load Ua types in node cache. + /// + [Test, Order(500)] + public void NodeCache_LoadUaDefinedTypes() + { + INodeCache nodeCache = Session.NodeCache; + Assert.IsNotNull(nodeCache); + + // load the predefined types + nodeCache.LoadUaDefinedTypes(Session.SystemContext); + + // reload the predefined types + nodeCache.LoadUaDefinedTypes(Session.SystemContext); + } + + /// + /// Browse all variables in the objects folder. + /// + [Test, Order(100)] + public async Task NodeCache_BrowseAllVariables() + { + var result = new List(); + var nodesToBrowse = new ExpandedNodeIdCollection { + ObjectIds.ObjectsFolder + }; + + await Session.FetchTypeTreeAsync(ReferenceTypeIds.References).ConfigureAwait(false); // TODO: Async + + while (nodesToBrowse.Count > 0) + { + var nextNodesToBrowse = new ExpandedNodeIdCollection(); + foreach (var node in nodesToBrowse) + { + try + { + var organizers = await Session.NodeCache.FindReferencesAsync( + node, + ReferenceTypeIds.HierarchicalReferences, + false, + true).ConfigureAwait(false); + nextNodesToBrowse.AddRange(organizers.Select(n => n.NodeId)); + var objectNodes = organizers.Where(n => n is ObjectNode); + var variableNodes = organizers.Where(n => n is VariableNode); + result.AddRange(variableNodes); + } + catch (ServiceResultException sre) + { + if (sre.StatusCode == StatusCodes.BadUserAccessDenied) + { + TestContext.Out.WriteLine($"Access denied: Skip node {node}."); + } + } + } + nodesToBrowse = new ExpandedNodeIdCollection(nextNodesToBrowse.Distinct()); + TestContext.Out.WriteLine("Found {0} duplicates", nextNodesToBrowse.Count - nodesToBrowse.Count); + } + + TestContext.Out.WriteLine("Found {0} variables", result.Count); + } + + /// + /// Browse all variables in the objects folder. + /// + [Test, Order(200)] + public async Task NodeCache_BrowseAllVariables_MultipleNodes() + { + var result = new List(); + var nodesToBrowse = new ExpandedNodeIdCollection { + ObjectIds.ObjectsFolder + }; + + await Session.FetchTypeTreeAsync(ReferenceTypeIds.References).ConfigureAwait(false); + var referenceTypeIds = new NodeIdCollection() { ReferenceTypeIds.HierarchicalReferences }; + while (nodesToBrowse.Count > 0) + { + var nextNodesToBrowse = new ExpandedNodeIdCollection(); + try + { + var organizers = await Session.NodeCache.FindReferencesAsync( + nodesToBrowse, + referenceTypeIds, + false, + true).ConfigureAwait(false); + nextNodesToBrowse.AddRange(organizers.Select(n => n.NodeId)); + var objectNodes = organizers.Where(n => n is ObjectNode); + var variableNodes = organizers.Where(n => n is VariableNode); + result.AddRange(variableNodes); + } + catch (ServiceResultException sre) + { + if (sre.StatusCode == StatusCodes.BadUserAccessDenied) + { + TestContext.Out.WriteLine($"Access denied: Skipped node."); + } + } + nodesToBrowse = new ExpandedNodeIdCollection(nextNodesToBrowse.Distinct()); + TestContext.Out.WriteLine("Found {0} duplicates", nextNodesToBrowse.Count - nodesToBrowse.Count); + } + + TestContext.Out.WriteLine("Found {0} variables", result.Count); + } + + [Test, Order(720)] + public async Task NodeCacheFind() + { + if (ReferenceDescriptions == null) + { + BrowseFullAddressSpace(); + } + + foreach (var reference in ReferenceDescriptions.Take(MaxReferences)) + { + var nodeId = ExpandedNodeId.ToNodeId(reference.NodeId, Session.NamespaceUris); + var node = await Session.NodeCache.FindAsync(reference.NodeId).ConfigureAwait(false); + TestContext.Out.WriteLine("NodeId: {0} Node: {1}", nodeId, node); + } + } + + [Test, Order(730)] + public async Task NodeCacheFetchNode() + { + if (ReferenceDescriptions == null) + { + BrowseFullAddressSpace(); + } + + foreach (var reference in ReferenceDescriptions.Take(MaxReferences)) + { + var nodeId = ExpandedNodeId.ToNodeId(reference.NodeId, Session.NamespaceUris); + var node = await Session.NodeCache.FetchNodeAsync(reference.NodeId).ConfigureAwait(false); + TestContext.Out.WriteLine("NodeId: {0} Node: {1}", nodeId, node); + } + } + + [Test, Order(740)] + public async Task NodeCacheFetchNodes() + { + if (ReferenceDescriptions == null) + { + BrowseFullAddressSpace(); + } + + var testSet = ReferenceDescriptions.Take(MaxReferences).Select(r => r.NodeId).ToList(); + IList nodeCollection = await Session.NodeCache.FetchNodesAsync(testSet).ConfigureAwait(false); + + foreach (var node in nodeCollection) + { + var nodeId = ExpandedNodeId.ToNodeId(node.NodeId, Session.NamespaceUris); + TestContext.Out.WriteLine("NodeId: {0} Node: {1}", nodeId, node); + } + } + + [Test, Order(750)] + public async Task NodeCacheFindReferences() + { + if (ReferenceDescriptions == null) + { + BrowseFullAddressSpace(); + } + + var testSet = ReferenceDescriptions.Take(MaxReferences).Select(r => r.NodeId).ToList(); + IList nodes = await Session.NodeCache.FindReferencesAsync(testSet, new NodeIdCollection() { ReferenceTypeIds.NonHierarchicalReferences }, false, true).ConfigureAwait(false); + + foreach (var node in nodes) + { + var nodeId = ExpandedNodeId.ToNodeId(node.NodeId, Session.NamespaceUris); + TestContext.Out.WriteLine("NodeId: {0} Node: {1}", nodeId, node); + } + } + + [Test, Order(900)] + public async Task FetchTypeTreeAsync() + { + await Session.FetchTypeTreeAsync(NodeId.ToExpandedNodeId(DataTypeIds.BaseDataType, Session.NamespaceUris)).ConfigureAwait(false); + } + + [Test, Order(910)] + public async Task FetchAllReferenceTypesAsync() + { + var bindingFlags = + BindingFlags.Instance | + BindingFlags.Static | + BindingFlags.Public; + var fieldValues = typeof(ReferenceTypeIds) + .GetFields(bindingFlags) + .Select(field => NodeId.ToExpandedNodeId((NodeId)field.GetValue(null), Session.NamespaceUris)); + + await Session.FetchTypeTreeAsync(new ExpandedNodeIdCollection(fieldValues)).ConfigureAwait(false); + } + + /// + /// Test concurrent access of FetchNodes. + /// + [Test, Order(1000)] + public async Task NodeCacheFetchNodesConcurrentAsync() + { + if (ReferenceDescriptions == null) + { + BrowseFullAddressSpace(); + } + + Random random = new Random(62541); + var testSet = ReferenceDescriptions.OrderBy(o => random.Next()).Take(kTestSetSize).Select(r => r.NodeId).ToList(); + var taskList = new List(); + + // test concurrent access of FetchNodes + for (int i = 0; i < 10; i++) + { + Task t = Session.NodeCache.FetchNodesAsync(testSet); + taskList.Add(t); + } + await Task.WhenAll(taskList.ToArray()).ConfigureAwait(false); + } + + /// + /// Test concurrent access of Find. + /// + [Test, Order(1100)] + public async Task NodeCacheFindNodesConcurrent() + { + if (ReferenceDescriptions == null) + { + BrowseFullAddressSpace(); + } + + Random random = new Random(62541); + var testSet = ReferenceDescriptions.OrderBy(o => random.Next()).Take(kTestSetSize).Select(r => r.NodeId).ToList(); + var taskList = new List(); + + // test concurrent access of FetchNodes + for (int i = 0; i < 10; i++) + { + Task t = Session.NodeCache.FindAsync(testSet); + taskList.Add(t); + } + await Task.WhenAll(taskList.ToArray()).ConfigureAwait(false); + } + + /// + /// Test concurrent access of FindReferences. + /// + [Test, Order(1200)] + public async Task NodeCacheFindReferencesConcurrent() + { + if (ReferenceDescriptions == null) + { + BrowseFullAddressSpace(); + } + + Random random = new Random(62541); + var testSet = ReferenceDescriptions.OrderBy(o => random.Next()).Take(kTestSetSize).Select(r => r.NodeId).ToList(); + var taskList = new List(); + var refTypeIds = new List() { ReferenceTypeIds.HierarchicalReferences }; + await FetchAllReferenceTypesAsync().ConfigureAwait(false); + + // test concurrent access of FetchNodes + for (int i = 0; i < 10; i++) + { + Task t = Session.NodeCache.FindReferencesAsync(testSet, refTypeIds, false, true); + taskList.Add(t); + } + await Task.WhenAll(taskList.ToArray()).ConfigureAwait(false); + } + + /// + /// Test concurrent access of many methods in INodecache interface + /// + [Test, Order(1300)] + public async Task NodeCacheTestAllMethodsConcurrently() + { + const int testCases = 10; + const int testCaseRunTime = 5_000; + + if (ReferenceDescriptions == null) + { + BrowseFullAddressSpace(); + } + + Random random = new Random(62541); + 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(); + var refTypeIds = new List() { ReferenceTypeIds.HierarchicalReferences }; + + // test concurrent access of many methods in INodecache interface + for (int i = 0; i < testCases; i++) + { + int iteration = i; + Task t = Task.Run(async () => { + DateTime start = DateTime.UtcNow; + do + { + switch (iteration) + { + case 0: + await FetchAllReferenceTypesAsync().ConfigureAwait(false); + IList result = await Session.NodeCache.FindReferencesAsync(testSet1, refTypeIds, false, true).ConfigureAwait(false); + break; + case 1: + IList result1 = await Session.NodeCache.FindAsync(testSet2).ConfigureAwait(false); + break; + case 2: + IList result2 = await Session.NodeCache.FetchNodesAsync(testSet3).ConfigureAwait(false); + string displayText = Session.NodeCache.GetDisplayText(result2[0]); + break; + case 3: + IList result3 = await Session.NodeCache.FindReferencesAsync(testSet1[0], refTypeIds[0], false, true).ConfigureAwait(false); + break; + case 4: + INode result4 = await Session.NodeCache.FindAsync(testSet2[0]).ConfigureAwait(false); + Assert.NotNull(result4); + Assert.True(result4 is VariableNode); + break; + case 5: + Node result5 = await Session.NodeCache.FetchNodeAsync(testSet3[0]).ConfigureAwait(false); + Assert.NotNull(result5); + Assert.True(result5 is VariableNode); + await Session.NodeCache.FetchSuperTypesAsync(result5.NodeId).ConfigureAwait(false); + break; + case 6: + string text = Session.NodeCache.GetDisplayText(testSet2[0]); + Assert.NotNull(text); + break; + case 7: + NodeId number = new NodeId((int)BuiltInType.Number); + bool isKnown = Session.NodeCache.IsKnown(new ExpandedNodeId((int)BuiltInType.Int64)); + Assert.True(isKnown); + bool isKnown2 = Session.NodeCache.IsKnown(TestData.DataTypeIds.ScalarStructureDataType); + Assert.True(isKnown2); + NodeId nodeId = await Session.NodeCache.FindSuperTypeAsync(TestData.DataTypeIds.Vector).ConfigureAwait(false); + Assert.AreEqual(DataTypeIds.Structure, nodeId); + NodeId nodeId2 = await Session.NodeCache.FindSuperTypeAsync(ExpandedNodeId.ToNodeId(TestData.DataTypeIds.Vector, Session.NamespaceUris)).ConfigureAwait(false); + Assert.AreEqual(DataTypeIds.Structure, nodeId2); + IList subTypes = Session.NodeCache.FindSubTypes(new ExpandedNodeId((int)BuiltInType.Number)); + bool isTypeOf = Session.NodeCache.IsTypeOf(new ExpandedNodeId((int)BuiltInType.Int32), new ExpandedNodeId((int)BuiltInType.Number)); + bool isTypeOf2 = Session.NodeCache.IsTypeOf(new NodeId((int)BuiltInType.UInt32), number); + break; + case 8: + bool isEncodingOf = Session.NodeCache.IsEncodingOf(new ExpandedNodeId((int)BuiltInType.Int32), DataTypeIds.Structure); + Assert.False(isEncodingOf); + bool isEncodingFor = Session.NodeCache.IsEncodingFor(DataTypeIds.Structure, + new TestData.ScalarStructureDataType()); + Assert.True(isEncodingFor); + bool isEncodingFor2 = Session.NodeCache.IsEncodingFor(new NodeId((int)BuiltInType.UInt32), new NodeId((int)BuiltInType.UInteger)); + Assert.False(isEncodingFor2); + break; + case 9: + NodeId findDataTypeId = Session.NodeCache.FindDataTypeId(new ExpandedNodeId((int)Objects.DataTypeAttributes_Encoding_DefaultBinary)); + NodeId findDataTypeId2 = Session.NodeCache.FindDataTypeId((int)Objects.DataTypeAttributes_Encoding_DefaultBinary); + break; + default: + Assert.Fail("Invalid test case"); + break; + } + } while ((DateTime.UtcNow - start).TotalMilliseconds < testCaseRunTime); + + }); + taskList.Add(t); + } + await Task.WhenAll(taskList.ToArray()).ConfigureAwait(false); + } + #endregion + + #region Benchmarks + #endregion + + #region Private Methods + private void BrowseFullAddressSpace() + { + var requestHeader = new RequestHeader { + Timestamp = DateTime.UtcNow, + TimeoutHint = MaxTimeout + }; + + // Session + var clientTestServices = new ClientTestServices(Session); + ReferenceDescriptions = CommonTestWorkers.BrowseFullAddressSpaceWorker(clientTestServices, requestHeader); + } + #endregion + } +} diff --git a/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj b/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj index 9fd1f08c4..ee6ed63f1 100644 --- a/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj +++ b/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj @@ -9,8 +9,8 @@ - - + + all diff --git a/Tests/Opc.Ua.Client.Tests/ReverseConnectTest.cs b/Tests/Opc.Ua.Client.Tests/ReverseConnectTest.cs index 08a872084..6c4fff4a5 100644 --- a/Tests/Opc.Ua.Client.Tests/ReverseConnectTest.cs +++ b/Tests/Opc.Ua.Client.Tests/ReverseConnectTest.cs @@ -140,11 +140,16 @@ public async Task GetEndpointsInternal() m_endpointUrl, null, cancellationTokenSource.Token).ConfigureAwait(false); Assert.NotNull(connection, "Failed to get connection."); } - var endpointConfiguration = EndpointConfiguration.Create(); - endpointConfiguration.OperationTimeout = MaxTimeout; - using (DiscoveryClient client = DiscoveryClient.Create(config, connection, endpointConfiguration)) + + using (var cancellationTokenSource = new CancellationTokenSource(MaxTimeout)) { - Endpoints = client.GetEndpoints(null); + var endpointConfiguration = EndpointConfiguration.Create(); + endpointConfiguration.OperationTimeout = MaxTimeout; + using (DiscoveryClient client = DiscoveryClient.Create(config, connection, endpointConfiguration)) + { + Endpoints = await client.GetEndpointsAsync(null, cancellationTokenSource.Token).ConfigureAwait(false); + await client.CloseAsync(cancellationTokenSource.Token).ConfigureAwait(false); + } } } @@ -188,9 +193,9 @@ public async Task ReverseConnect(string securityPolicy) // connect #if NET6_0_OR_GREATER - var sessionfactory = TraceableSessionFactory.Instance; + var sessionfactory = HeaderUpdatingSessionFactory.Instance; #else - var sessionfactory = DefaultSessionFactory.Instance; + var sessionfactory = TestableSessionFactory.Instance; #endif var session = await sessionfactory.CreateAsync(config, connection, endpoint, false, false, "Reverse Connect Client", MaxTimeout, new UserIdentity(new AnonymousIdentityToken()), null).ConfigureAwait(false); @@ -232,9 +237,9 @@ public async Task ReverseConnect2(bool updateBeforeConnect, bool checkDomain) // connect #if NET6_0_OR_GREATER - var sessionfactory = TraceableSessionFactory.Instance; + var sessionfactory = HeaderUpdatingSessionFactory.Instance; #else - var sessionfactory = DefaultSessionFactory.Instance; + var sessionfactory = TestableSessionFactory.Instance; #endif var session = await sessionfactory.CreateAsync(config, ClientFixture.ReverseConnectManager, endpoint, updateBeforeConnect, checkDomain, "Reverse Connect Client", MaxTimeout, new UserIdentity(new AnonymousIdentityToken()), null).ConfigureAwait(false); diff --git a/Tests/Opc.Ua.Client.Tests/SessionClientBatchTest.cs b/Tests/Opc.Ua.Client.Tests/SessionClientBatchTest.cs index b75acd55b..b17d13709 100644 --- a/Tests/Opc.Ua.Client.Tests/SessionClientBatchTest.cs +++ b/Tests/Opc.Ua.Client.Tests/SessionClientBatchTest.cs @@ -48,6 +48,8 @@ namespace Opc.Ua.Client.Tests public class SessionClientBatchTest : ClientTestFramework { public const uint kOperationLimit = 5; + private Random m_random; + public SessionClientBatchTest(string uriScheme = Utils.UriSchemeOpcTcp) : base(uriScheme) { @@ -80,6 +82,7 @@ public SessionClientBatchTest(string uriScheme = Utils.UriSchemeOpcTcp) : MaxNodesPerWrite = kOperationLimit }; } + m_random = new Random(0x62541); } /// @@ -98,6 +101,15 @@ public SessionClientBatchTest(string uriScheme = Utils.UriSchemeOpcTcp) : public new async Task SetUp() { await base.SetUp().ConfigureAwait(false); + + // test if the server accepts RequestHeader timestampes which + // are up to +-5 days off. + if (Session is TestableSession testableSession) + { + // set the time offset to a value from -5 to +5 days + testableSession.TimestampOffset = TimeSpan.FromSeconds((m_random.NextDouble() - 0.5) * 3600.0 * 24.0 * 10.0); + TestContext.Out.WriteLine("The time offset for request headers has been set to {0} offset.", testableSession.TimestampOffset.ToString()); + } } /// diff --git a/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs b/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs index 393fa0f50..9c408f68d 100644 --- a/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs +++ b/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs @@ -95,7 +95,7 @@ public class SubscriptionTest : ClientTestFramework [Test, Order(100)] public void AddSubscription() { - var subscription = new Subscription(); + var subscription = new TestableSubscription(); // check keepAlive int keepAlive = 0; @@ -103,7 +103,7 @@ public void AddSubscription() // add current time var list = new List { - new MonitoredItem(subscription.DefaultItem) + new TestableMonitoredItem(subscription.DefaultItem) { DisplayName = "ServerStatusCurrentTime", StartNodeId = VariableIds.Server_ServerStatus_CurrentTime } @@ -115,7 +115,7 @@ public void AddSubscription() } }); - subscription = new Subscription(Session.DefaultSubscription); + subscription = new TestableSubscription(Session.DefaultSubscription); TestContext.Out.WriteLine("MaxMessageCount: {0}", subscription.MaxMessageCount); TestContext.Out.WriteLine("MaxNotificationsPerPublish: {0}", subscription.MaxNotificationsPerPublish); TestContext.Out.WriteLine("MinLifetimeInterval: {0}", subscription.MinLifetimeInterval); @@ -133,7 +133,7 @@ public void AddSubscription() // add state var list2 = new List { - new MonitoredItem(subscription.DefaultItem) + new TestableMonitoredItem(subscription.DefaultItem) { DisplayName = "ServerStatusState", StartNodeId = VariableIds.Server_ServerStatus_State }, @@ -158,8 +158,8 @@ public void AddSubscription() subscription.Priority = 200; subscription.Modify(); - // save - Session.Save(m_subscriptionTestXml); + // save with custom Subscription subclass information + Session.Save(m_subscriptionTestXml, new[] { typeof(TestableSubscription) }); Thread.Sleep(5000); OutputSubscriptionInfo(TestContext.Out, subscription); @@ -168,6 +168,16 @@ public void AddSubscription() var sre = Assert.Throws(() => subscription.Republish(subscription.SequenceNumber + 100)); Assert.AreEqual(StatusCodes.BadMessageNotAvailable, sre.StatusCode); + // verify that reconnect created subclassed version of subscription and monitored item + foreach (var s in Session.Subscriptions) + { + Assert.AreEqual(typeof(TestableSubscription), s.GetType()); + foreach (var m in s.MonitoredItems) + { + Assert.AreEqual(typeof(TestableMonitoredItem), m.GetType()); + } + } + subscription.RemoveItems(list); subscription.ApplyChanges(); @@ -180,7 +190,7 @@ public void AddSubscription() [Test, Order(110)] public async Task AddSubscriptionAsync() { - var subscription = new Subscription(); + var subscription = new TestableSubscription(); // check keepAlive int keepAlive = 0; @@ -191,7 +201,7 @@ public async Task AddSubscriptionAsync() // add current time var list = new List { - new MonitoredItem(subscription.DefaultItem) + new TestableMonitoredItem(subscription.DefaultItem) { DisplayName = "ServerStatusCurrentTime", StartNodeId = VariableIds.Server_ServerStatus_CurrentTime } @@ -203,7 +213,7 @@ public async Task AddSubscriptionAsync() } }); - subscription = new Subscription(Session.DefaultSubscription); + subscription = new TestableSubscription(Session.DefaultSubscription); TestContext.Out.WriteLine("MaxMessageCount: {0}", subscription.MaxMessageCount); TestContext.Out.WriteLine("MaxNotificationsPerPublish: {0}", subscription.MaxNotificationsPerPublish); @@ -234,7 +244,7 @@ public async Task AddSubscriptionAsync() // add state var list2 = new List { - new MonitoredItem(subscription.DefaultItem) + new TestableMonitoredItem(subscription.DefaultItem) { DisplayName = "ServerStatusState", StartNodeId = VariableIds.Server_ServerStatus_State }, @@ -259,15 +269,25 @@ public async Task AddSubscriptionAsync() subscription.Priority = 200; await subscription.ModifyAsync().ConfigureAwait(false); - // save - Session.Save(m_subscriptionTestXml); + // save with custom Subscription subclass information + Session.Save(m_subscriptionTestXml, new[] { typeof(TestableSubscription) }); await Task.Delay(5000).ConfigureAwait(false); OutputSubscriptionInfo(TestContext.Out, subscription); await subscription.ConditionRefreshAsync().ConfigureAwait(false); - var sre = Assert.Throws(() => subscription.Republish(subscription.SequenceNumber)); - Assert.AreEqual(StatusCodes.BadMessageNotAvailable, sre.StatusCode); + var sre = Assert.ThrowsAsync(async () => await subscription.RepublishAsync(subscription.SequenceNumber).ConfigureAwait(false)); + Assert.AreEqual(StatusCodes.BadMessageNotAvailable, sre.StatusCode, $"Expected BadMessageNotAvailable, but received {sre.Message}"); + + // verify that reconnect created subclassed version of subscription and monitored item + foreach (var s in Session.Subscriptions) + { + Assert.AreEqual(typeof(TestableSubscription), s.GetType()); + foreach (var m in s.MonitoredItems) + { + Assert.AreEqual(typeof(TestableMonitoredItem), m.GetType()); + } + } subscription.RemoveItems(list); await subscription.ApplyChangesAsync().ConfigureAwait(false); @@ -284,7 +304,7 @@ public async Task LoadSubscriptionAsync() if (!File.Exists(m_subscriptionTestXml)) Assert.Ignore("Save file {0} does not exist yet", m_subscriptionTestXml); // load - var subscriptions = Session.Load(m_subscriptionTestXml); + var subscriptions = Session.Load(m_subscriptionTestXml, false, new[] { typeof(TestableSubscription) }); Assert.NotNull(subscriptions); Assert.IsNotEmpty(subscriptions); @@ -343,7 +363,7 @@ public void SequentialPublishingSubscription(bool enabled) // multiple Subscriptions to enforce multiple queued publish requests for (int i = 0; i < subscriptions; i++) { - var s = new Subscription(Session.DefaultSubscription) { + var s = new TestableSubscription(Session.DefaultSubscription) { SequentialPublishing = enabled, KeepAliveCount = 10, PublishingInterval = 100, @@ -491,7 +511,7 @@ public async Task ReconnectWithSavedSessionSecrets( var originSubscriptionFastDataCounters = new int[kTestSubscriptions]; var targetSubscriptionCounters = new int[kTestSubscriptions]; var targetSubscriptionFastDataCounters = new int[kTestSubscriptions]; - var subscriptionTemplate = new Subscription(session1.DefaultSubscription) { + var subscriptionTemplate = new TestableSubscription(session1.DefaultSubscription) { PublishingInterval = 1_000, KeepAliveCount = 5, PublishingEnabled = true, @@ -515,7 +535,7 @@ public async Task ReconnectWithSavedSessionSecrets( TestContext.Out.WriteLine(Encoding.UTF8.GetString(configStreamArray)); var subscriptionStream = new MemoryStream(); - session1.Save(subscriptionStream, session1.Subscriptions); + session1.Save(subscriptionStream, session1.Subscriptions, new[] { typeof(TestableSubscription) }); var subscriptionStreamArray = subscriptionStream.ToArray(); TestContext.Out.WriteLine($"Subscriptions: {subscriptionStreamArray.Length} bytes"); @@ -540,7 +560,7 @@ public async Task ReconnectWithSavedSessionSecrets( // restore the subscriptions var loadSubscriptionStream = new MemoryStream(subscriptionStreamArray); - var restoredSubscriptions = new SubscriptionCollection(session2.Load(loadSubscriptionStream, true)); + var restoredSubscriptions = new SubscriptionCollection(session2.Load(loadSubscriptionStream, true, new[] { typeof(TestableSubscription) })); // hook notifications for log output int ii = 0; @@ -569,7 +589,14 @@ public async Task ReconnectWithSavedSessionSecrets( }; // activate the session from saved sesson secrets on the new channel - session2.Reconnect(channel2); + if (asyncTest) + { + await session2.ReconnectAsync(channel2).ConfigureAwait(false); + } + else + { + session2.Reconnect(channel2); + } // reactivate restored subscriptions if (asyncTest) @@ -583,7 +610,7 @@ public async Task ReconnectWithSavedSessionSecrets( Assert.IsTrue(reactivateResult); } - await Task.Delay(kDelay).ConfigureAwait(false); + await Task.Delay(2 * kDelay).ConfigureAwait(false); Assert.AreEqual(session1.SessionId, session2.SessionId); @@ -601,27 +628,38 @@ public async Task ReconnectWithSavedSessionSecrets( for (ii = 0; ii < kTestSubscriptions; ii++) { var monitoredItemCount = restoredSubscriptions[ii].MonitoredItemCount; + string errorText = $"Error in test subscription {ii}"; // the static subscription doesn't resend data until there is a data change if (ii == 0 && !sendInitialValues) { - Assert.AreEqual(0, targetSubscriptionCounters[ii]); - Assert.AreEqual(0, targetSubscriptionFastDataCounters[ii]); + Assert.AreEqual(0, targetSubscriptionCounters[ii], errorText); + Assert.AreEqual(0, targetSubscriptionFastDataCounters[ii], errorText); } else if (ii == 0) { - Assert.AreEqual(10, targetSubscriptionCounters[ii]); - Assert.AreEqual(1, targetSubscriptionFastDataCounters[ii]); + Assert.AreEqual(monitoredItemCount, targetSubscriptionCounters[ii], errorText); + Assert.AreEqual(1, targetSubscriptionFastDataCounters[ii], errorText); } else { - Assert.LessOrEqual(monitoredItemCount, targetSubscriptionCounters[ii]); - Assert.LessOrEqual(1, targetSubscriptionFastDataCounters[ii]); + Assert.LessOrEqual(monitoredItemCount, targetSubscriptionCounters[ii], errorText); + Assert.LessOrEqual(1, targetSubscriptionFastDataCounters[ii], errorText); } } await Task.Delay(kDelay).ConfigureAwait(false); + // verify that reconnect created subclassed version of subscription and monitored item + foreach (var s in session2.Subscriptions) + { + Assert.AreEqual(typeof(TestableSubscription), s.GetType()); + foreach (var m in s.MonitoredItems) + { + Assert.AreEqual(typeof(TestableMonitoredItem), m.GetType()); + } + } + // cannot read using a closed channel, validate the status code if (endpoint.EndpointUrl.ToString().StartsWith(Utils.UriSchemeOpcTcp, StringComparison.Ordinal)) { @@ -635,11 +673,18 @@ public async Task ReconnectWithSavedSessionSecrets( } session1.DeleteSubscriptionsOnClose = true; - session1.Close(1000); - Utils.SilentDispose(session1); - session2.DeleteSubscriptionsOnClose = true; - session2.Close(1000); + if (asyncTest) + { + await session1.CloseAsync(1000).ConfigureAwait(false); + await session2.CloseAsync(1000).ConfigureAwait(false); + } + else + { + session1.Close(1000); + session2.Close(1000); + } + Utils.SilentDispose(session1); Utils.SilentDispose(session2); Assert.AreEqual(0, session1ConfigChanged); @@ -659,7 +704,7 @@ public async Task PublishRequestCount() for (int i = 0; i < subscriptions; i++) { - var subscription = new Subscription(Session.DefaultSubscription) { + var subscription = new TestableSubscription(Session.DefaultSubscription) { PublishingInterval = 0, DisableMonitoredItemCache = true, PublishingEnabled = true @@ -678,7 +723,7 @@ public async Task PublishRequestCount() for (int ii = 0; ii < monitoredItemsPerSubscription; ii++) { var nextNode = nodeSet[ii % nodeSet.Count]; - list.Add(new MonitoredItem(subscription.DefaultItem) { + list.Add(new TestableMonitoredItem(subscription.DefaultItem) { StartNodeId = nextNode, SamplingInterval = 0 }); @@ -769,7 +814,7 @@ public async Task TransferSubscriptionAsync(TransferType transferType, bool send var targetSubscriptionCounters = new int[kTestSubscriptions]; var targetSubscriptionFastDataCounters = new int[kTestSubscriptions]; var originSubscriptionTransferred = new int[kTestSubscriptions]; - var subscriptionTemplate = new Subscription(originSession.DefaultSubscription) { + var subscriptionTemplate = new TestableSubscription(originSession.DefaultSubscription) { PublishingInterval = 1_000, LifetimeCount = 30, KeepAliveCount = 5, @@ -782,13 +827,13 @@ public async Task TransferSubscriptionAsync(TransferType transferType, bool send originSubscriptions, originSubscriptionCounters, originSubscriptionFastDataCounters, kTestSubscriptions, kQueueSize); - if(TransferType.KeepOpen == transferType) + if (TransferType.KeepOpen == transferType) { foreach (var subscription in originSubscriptions) { subscription.PublishStatusChanged += (s, e) => { TestContext.Out.WriteLine($"PublishStatusChanged: {s.Session.SessionId}-{s.Id}-{e.Status}"); - if ((e.Status & PublishStateChangedMask.Transferred)!=0) + if ((e.Status & PublishStateChangedMask.Transferred) != 0) { // subscription transferred Interlocked.Increment(ref originSubscriptionTransferred[(int)s.Handle]); @@ -807,7 +852,10 @@ public async Task TransferSubscriptionAsync(TransferType transferType, bool send if (transferType != TransferType.KeepOpen) { originSession.DeleteSubscriptionsOnClose = false; - originSession.Save(filePath); + + // save with custom Subscription subclass information + originSession.Save(filePath, new[] { typeof(TestableSubscription) }); + if (transferType == TransferType.CloseSession) { // graceful close @@ -850,7 +898,7 @@ public async Task TransferSubscriptionAsync(TransferType transferType, bool send if (transferType != TransferType.KeepOpen) { // load subscriptions for transfer - transferSubscriptions.AddRange(targetSession.Load(filePath, true)); + transferSubscriptions.AddRange(targetSession.Load(filePath, true, new[] { typeof(TestableSubscription) })); // hook notifications for log output int ii = 0; @@ -930,7 +978,7 @@ public async Task TransferSubscriptionAsync(TransferType transferType, bool send TestContext.Out.WriteLine("TargetSession is now SessionId={0}", targetSession.SessionId); // wait for some events - await Task.Delay(kDelay).ConfigureAwait(false); + await Task.Delay(2 * kDelay).ConfigureAwait(false); if (TransferType.KeepOpen == transferType) { @@ -940,6 +988,7 @@ public async Task TransferSubscriptionAsync(TransferType transferType, bool send Assert.AreEqual(1, originSubscriptionTransferred[(int)subscription.Handle]); } } + // stop publishing foreach (var subscription in transferSubscriptions) { @@ -993,7 +1042,7 @@ public async Task TransferSubscriptionAsync(TransferType transferType, bool send } // wait for some events - await Task.Delay(kDelay).ConfigureAwait(false); + await Task.Delay(2 * kDelay).ConfigureAwait(false); // validate expected counts for (int jj = 0; jj < kTestSubscriptions; jj++) @@ -1054,10 +1103,8 @@ public async Task TransferSubscriptionAsync(TransferType transferType, bool send [Test, Order(1000)] public void FastKeepAliveCallback() { - var subscription = new Subscription(); - // add current time - subscription = new Subscription(Session.DefaultSubscription) { + var subscription = new TestableSubscription(Session.DefaultSubscription) { KeepAliveCount = 1, PublishingInterval = 250, }; @@ -1067,7 +1114,7 @@ public void FastKeepAliveCallback() // add static nodes var list = new List { - new MonitoredItem(subscription.DefaultItem) + new TestableMonitoredItem(subscription.DefaultItem) { DisplayName = "ServerStatusState", StartNodeId = VariableIds.Server_ServerStatus_State }, @@ -1157,7 +1204,7 @@ private void CreateSubscriptions( for (int ii = 0; ii < subscriptionCount; ii++) { // create subscription with static monitored items - var subscription = new Subscription(template) { + var subscription = new TestableSubscription(template) { PublishingEnabled = true, Handle = ii, FastDataChangeCallback = (s, n, _) => { @@ -1213,7 +1260,7 @@ private IList CreateMonitoredItemTestSet(Subscription subscriptio var list = new List(); foreach (NodeId nodeId in nodeIds) { - var item = new MonitoredItem(subscription.DefaultItem) { + var item = new TestableMonitoredItem(subscription.DefaultItem) { StartNodeId = nodeId }; list.Add(item); diff --git a/Tests/Opc.Ua.Client.Tests/TestableSession.cs b/Tests/Opc.Ua.Client.Tests/TestableSession.cs new file mode 100644 index 000000000..7d162d187 --- /dev/null +++ b/Tests/Opc.Ua.Client.Tests/TestableSession.cs @@ -0,0 +1,248 @@ +/* ======================================================================== + * Copyright (c) 2005-2023 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.Runtime.Serialization; +using System.Security.Cryptography.X509Certificates; + +namespace Opc.Ua.Client.Tests +{ + #region Namespace Declarations + /// + public static partial class Namespaces + { + /// + /// The URI for the OpcUaClient namespace (.NET code namespace is 'Opc.Ua.Client'). + /// + public const string OpcUaClient = "http://opcfoundation.org/UA/Client/Types.xsd"; + } + #endregion + + /// + /// A subclass of a session for testing purposes, e.g. to override some implementations. + /// + [DataContract(Namespace = Namespaces.OpcUaClient)] + [KnownType(typeof(TestableSubscription))] + [KnownType(typeof(TestableMonitoredItem))] + public class TestableSession : Session + { + #region Constructors + /// + /// Constructs a new instance of the class. + /// + /// The channel used to communicate with the server. + /// The configuration for the client application. + /// The endpoint use to initialize the channel. + public TestableSession( + ISessionChannel channel, + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint) + : + this(channel as ITransportChannel, configuration, endpoint, null) + { + } + + /// + /// Constructs a new instance of the class. + /// + /// The channel used to communicate with the server. + /// The configuration for the client application. + /// The endpoint used to initialize the channel. + /// The certificate to use for the client. + /// The list of available endpoints returned by server in GetEndpoints() response. + /// The value of profileUris used in GetEndpoints() request. + /// + /// The application configuration is used to look up the certificate if none is provided. + /// The clientCertificate must have the private key. This will require that the certificate + /// be loaded from a certicate store. Converting a DER encoded blob to a X509Certificate2 + /// will not include a private key. + /// The availableEndpoints and discoveryProfileUris parameters are used to validate + /// that the list of EndpointDescriptions returned at GetEndpoints matches the list returned at CreateSession. + /// + public TestableSession( + ITransportChannel channel, + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint, + X509Certificate2 clientCertificate, + EndpointDescriptionCollection availableEndpoints = null, + StringCollection discoveryProfileUris = null) + : base(channel, configuration, endpoint, clientCertificate, availableEndpoints, discoveryProfileUris) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The channel. + /// The template session. + /// if set to true the event handlers are copied. + public TestableSession(ITransportChannel channel, Session template, bool copyEventHandlers) + : + base(channel, template, copyEventHandlers) + { + } + #endregion + + /// + /// The timespan offset to be used to modify the request header timestamp. + /// + [DataMember] + public TimeSpan TimestampOffset { get; set; } = new TimeSpan(0); + + /// + protected override void UpdateRequestHeader(IServiceRequest request, bool useDefaults, string serviceName) + { + base.UpdateRequestHeader(request, useDefaults, serviceName); + request.RequestHeader.Timestamp = request.RequestHeader.Timestamp + TimestampOffset; + } + + /// + public override Session CloneSession(ITransportChannel channel, bool copyEventHandlers) + { + return new TestableSession(channel, this, copyEventHandlers) { + TimestampOffset = this.TimestampOffset, + }; + } + } + + /// + /// A subclass of the subscription for testing purposes. + /// + [DataContract(Namespace = Namespaces.OpcUaClient)] + [KnownType(typeof(TestableMonitoredItem))] + public class TestableSubscription : Subscription + { + #region Constructors + /// + /// Constructs a new instance of the class. + /// + public TestableSubscription() + { + } + + /// + /// Constructs a new instance of the class. + /// + public TestableSubscription(Subscription template) + : this (template, false) + { + } + + /// + /// Constructs a new instance of the class. + /// + public TestableSubscription(Subscription template, bool copyEventHandlers) + : base(template, copyEventHandlers) + { + Initialize(); + } + + /// + /// Called by the .NET framework during deserialization. + /// + [OnDeserializing] + protected new void Initialize(StreamingContext context) + { + base.Initialize(context); + Initialize(); + } + + /// + /// Sets the private members to default values. + /// + private void Initialize() + { + } + #endregion + + /// + public override Subscription CloneSubscription(bool copyEventHandlers) + { + return new TestableSubscription(this, copyEventHandlers); + } + } + + /// + /// A subclass of a monitored item for testing purposes. + /// + [DataContract(Namespace = Namespaces.OpcUaClient)] + [KnownType(typeof(TestableMonitoredItem))] + public class TestableMonitoredItem : MonitoredItem + { + #region Constructors + /// + /// Constructs a new instance of the class. + /// + public TestableMonitoredItem() + { + } + + /// + /// Constructs a new instance of the class. + /// + public TestableMonitoredItem(MonitoredItem template) + : this(template, false, false) + { + + } + + /// + /// Constructs a new instance of the class. + /// + public TestableMonitoredItem(MonitoredItem template, bool copyEventHandlers, bool copyClientHandle) + : base(template, copyEventHandlers, copyClientHandle) + { + } + + /// + /// Called by the .NET framework during deserialization. + /// + [OnDeserializing] + protected new void Initialize(StreamingContext context) + { + base.Initialize(context); + Initialize(); + } + + /// + /// Sets the private members to default values. + /// + private void Initialize() + { + } + #endregion + + /// + public override MonitoredItem CloneMonitoredItem(bool copyEventHandlers, bool copyClientHandle) + { + return new TestableMonitoredItem(this, copyEventHandlers, copyClientHandle); + } + } + +} diff --git a/Tests/Opc.Ua.Client.Tests/TestableSessionFactory.cs b/Tests/Opc.Ua.Client.Tests/TestableSessionFactory.cs new file mode 100644 index 000000000..13d753606 --- /dev/null +++ b/Tests/Opc.Ua.Client.Tests/TestableSessionFactory.cs @@ -0,0 +1,194 @@ +/* ======================================================================== + * Copyright (c) 2005-2022 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.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; + +namespace Opc.Ua.Client.Tests +{ + /// + /// Object that creates instances of an Opc.Ua.Client.Session object. + /// + public class TestableSessionFactory : DefaultSessionFactory + { + /// + /// The default instance of the factory. + /// + public new static readonly TestableSessionFactory Instance = new TestableSessionFactory(); + + /// + /// Force use of the default instance. + /// + protected TestableSessionFactory() + { + } + + #region ISessionFactory Members + /// + public override Task CreateAsync( + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint, + bool updateBeforeConnect, + string sessionName, + uint sessionTimeout, + IUserIdentity identity, + IList preferredLocales, + CancellationToken ct = default) + { + return CreateAsync(configuration, endpoint, updateBeforeConnect, false, sessionName, sessionTimeout, identity, preferredLocales, ct); + } + + /// + public async override Task CreateAsync( + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint, + bool updateBeforeConnect, + bool checkDomain, + string sessionName, + uint sessionTimeout, + IUserIdentity identity, + IList preferredLocales, + CancellationToken ct = default) + { + return await Session.Create(this, configuration, (ITransportWaitingConnection)null, endpoint, + updateBeforeConnect, checkDomain, sessionName, sessionTimeout, + identity, preferredLocales, ct).ConfigureAwait(false); + } + + /// + public async override Task CreateAsync( + ApplicationConfiguration configuration, + ITransportWaitingConnection connection, + ConfiguredEndpoint endpoint, + bool updateBeforeConnect, + bool checkDomain, + string sessionName, + uint sessionTimeout, + IUserIdentity identity, + IList preferredLocales, + CancellationToken ct = default) + { + return await Session.Create(this, configuration, connection, endpoint, + updateBeforeConnect, checkDomain, sessionName, sessionTimeout, + identity, preferredLocales, ct + ).ConfigureAwait(false); + } + + /// + public async override Task CreateAsync( + ApplicationConfiguration configuration, + ReverseConnectManager reverseConnectManager, + ConfiguredEndpoint endpoint, + bool updateBeforeConnect, + bool checkDomain, + string sessionName, + uint sessionTimeout, + IUserIdentity userIdentity, + IList preferredLocales, + CancellationToken ct = default + ) + { + if (reverseConnectManager == null) + { + return await this.CreateAsync(configuration, endpoint, updateBeforeConnect, + checkDomain, sessionName, sessionTimeout, userIdentity, preferredLocales, ct).ConfigureAwait(false); + } + + ITransportWaitingConnection connection; + do + { + connection = await reverseConnectManager.WaitForConnection( + endpoint.EndpointUrl, + endpoint.ReverseConnect?.ServerUri, + ct).ConfigureAwait(false); + + if (updateBeforeConnect) + { + await endpoint.UpdateFromServerAsync( + endpoint.EndpointUrl, connection, + endpoint.Description.SecurityMode, + endpoint.Description.SecurityPolicyUri, + ct).ConfigureAwait(false); + updateBeforeConnect = false; + connection = null; + } + } while (connection == null); + + return await CreateAsync( + configuration, + connection, + endpoint, + false, + checkDomain, + sessionName, + sessionTimeout, + userIdentity, + preferredLocales, + ct).ConfigureAwait(false); + } + + /// + public override ISession Create( + ApplicationConfiguration configuration, + ITransportChannel channel, + ConfiguredEndpoint endpoint, + X509Certificate2 clientCertificate, + EndpointDescriptionCollection availableEndpoints = null, + StringCollection discoveryProfileUris = null) + { + return Session.Create(this, configuration, channel, endpoint, clientCertificate, availableEndpoints, discoveryProfileUris); + } + #endregion + + #region ISessionInstantiator Members + /// + public override Session Create( + ISessionChannel channel, + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint) + { + return new TestableSession(channel, configuration, endpoint); + } + + /// + public override Session Create( + ITransportChannel channel, + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint, + X509Certificate2 clientCertificate, + EndpointDescriptionCollection availableEndpoints = null, + StringCollection discoveryProfileUris = null) + { + return new TestableSession(channel, configuration, endpoint, clientCertificate, availableEndpoints, discoveryProfileUris); + } + #endregion + } +} diff --git a/Tests/Opc.Ua.Client.Tests/TestableSessionInstantiator.cs b/Tests/Opc.Ua.Client.Tests/TestableSessionInstantiator.cs new file mode 100644 index 000000000..e7a8e69ea --- /dev/null +++ b/Tests/Opc.Ua.Client.Tests/TestableSessionInstantiator.cs @@ -0,0 +1,64 @@ +/* ======================================================================== + * Copyright (c) 2005-2023 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.Security.Cryptography.X509Certificates; + +namespace Opc.Ua.Client.Tests +{ + /// + /// Object that creates an instance of a Session object. + /// It can be used to create instances of enhanced Session + /// classes with added functionality or overridden methods. + /// + public class TestableSessionInstantiator : ISessionInstantiator + { + #region Constructors + /// + public Session Create( + ISessionChannel channel, + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint) + { + return new TestableSession(channel, configuration, endpoint); + } + + /// + public Session Create( + ITransportChannel channel, + ApplicationConfiguration configuration, + ConfiguredEndpoint endpoint, + X509Certificate2 clientCertificate, + EndpointDescriptionCollection availableEndpoints = null, + StringCollection discoveryProfileUris = null) + { + return new TestableSession(channel, configuration, endpoint, clientCertificate, availableEndpoints, discoveryProfileUris); + } + #endregion + } +} diff --git a/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs b/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs index 1e42986c7..5d1467bb3 100644 --- a/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs +++ b/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs @@ -32,6 +32,7 @@ using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; +using System.Threading; using System.Threading.Tasks; using NUnit.Framework; @@ -533,6 +534,89 @@ public async Task TestInvalidAppCertChainDoNotRecreate(InvalidCertType certType, } } } + + /// + /// Tests that a supplied certifiacte is stored in the Trusted store of the Server after calling method AddOwnCertificateToTrustedStoreAsync + /// + /// + [Test] + public async Task TestAddOwnCertificateToTrustedStore() + { + //Arrange Application Instance + var applicationInstance = new ApplicationInstance() { + ApplicationName = ApplicationName + }; + ApplicationConfiguration configuration = await applicationInstance.Build(ApplicationUri, ProductUri) + .SetOperationTimeout(10000) + .AsServer(new string[] { EndpointUrl }) + .AddSecurityConfiguration(SubjectName, m_pkiRoot) + .Create().ConfigureAwait(false); + + //Arrange cert + DateTime notBefore = DateTime.Today.AddDays(-30); + DateTime notAfter = DateTime.Today.AddDays(30); + + var cert = CertificateFactory.CreateCertificate(SubjectName) + .SetNotBefore(notBefore) + .SetNotAfter(notAfter) + .SetCAConstraint(-1) + .CreateForRSA(); + + //Act + await applicationInstance.AddOwnCertificateToTrustedStoreAsync(cert, new CancellationToken()).ConfigureAwait(false); + ICertificateStore store = configuration.SecurityConfiguration.TrustedPeerCertificates.OpenStore(); + var storedCertificates = await store.FindByThumbprint(cert.Thumbprint).ConfigureAwait(false); + + //Assert + Assert.IsTrue(storedCertificates.Contains(cert)); + } + + /// + /// Test to verify that a new cert is not recreated/replaced if DisableCertificateAutoCreation is set. + /// + [Theory] + public async Task TestDisableCertificateAutoCreationAsync(bool server, bool disableCertificateAutoCreation) + { + // pki directory root for test runs. + var pkiRoot = Path.GetTempPath() + Path.GetRandomFileName() + Path.DirectorySeparatorChar; + + var applicationInstance = new ApplicationInstance() { + ApplicationName = ApplicationName, + DisableCertificateAutoCreation = disableCertificateAutoCreation + }; + Assert.NotNull(applicationInstance); + ApplicationConfiguration config; + if (server) + { + config = await applicationInstance.Build(ApplicationUri, ProductUri) + .AsServer(new string[] { "opc.tcp://localhost:12345/Configuration" }) + .AddSecurityConfiguration(SubjectName, pkiRoot) + .Create().ConfigureAwait(false); + } + else + { + config = await applicationInstance.Build(ApplicationUri, ProductUri) + .AsClient() + .AddSecurityConfiguration(SubjectName, pkiRoot) + .Create().ConfigureAwait(false); + } + Assert.NotNull(config); + + CertificateIdentifier applicationCertificate = applicationInstance.ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate; + Assert.IsNull(applicationCertificate.Certificate); + + if (disableCertificateAutoCreation) + { + var sre = Assert.ThrowsAsync(async () => + await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false)); + Assert.AreEqual(StatusCodes.BadConfigurationError, sre.StatusCode); + } + else + { + bool certOK = await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + Assert.True(certOK); + } + } #endregion #region Private Methods diff --git a/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj b/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj index c9a00a5d4..9f021cd26 100644 --- a/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj +++ b/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj @@ -8,8 +8,8 @@ - - + + all @@ -19,7 +19,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj b/Tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj index fbdf0a20b..aba14b86a 100644 --- a/Tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj +++ b/Tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj @@ -9,9 +9,9 @@ - - - + + + all @@ -24,6 +24,18 @@ + + + + + + + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);SIGNASSEMBLY diff --git a/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateValidatorAlternate.cs b/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateValidatorAlternate.cs index 42a04ad34..87709ed8f 100644 --- a/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateValidatorAlternate.cs +++ b/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateValidatorAlternate.cs @@ -32,6 +32,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading; @@ -40,6 +41,9 @@ using EmbedIO.Actions; using NUnit.Framework; using Opc.Ua.Security.Certificates; +#if !ECC_SUPPORT +using X509SignatureGenerator = Opc.Ua.Security.Certificates.X509SignatureGenerator; +#endif namespace Opc.Ua.Core.Tests.Security.Certificates { @@ -86,6 +90,14 @@ public CertificateValidatorAlternate(string altSubject = kCaSubject) [OneTimeSetUp] public async Task OneTimeSetUpAsync() { +#if NETCOREAPP2_1_OR_GREATER && !ECC_SUPPORT + // this test cannot create the required certs on macOS with legacy bouncy castle support + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Assert.Ignore("Creating the alternate certifcates via Pfx is not supported on mac OS."); + } +#endif + var crlName = "root.crl"; m_webServerUrl = "http://127.0.0.1:9696/"; @@ -197,7 +209,7 @@ public void CertificateWithAuthorityKeyID(bool subjectKeyIdentifier, bool issuer var ski = m_rootCert.Extensions.OfType().Single(); // create a certificate with special key info - X509Extension authorityKeyIdentifier = new X509AuthorityKeyIdentifierExtension( + var authorityKeyIdentifier = new Ua.Security.Certificates.X509AuthorityKeyIdentifierExtension( (byte[])(subjectKeyIdentifier ? ski.SubjectKeyIdentifier.FromHexString() : null), (X500DistinguishedName)(issuerName ? m_rootCert.IssuerName : null), (byte[])(serialNumber ? m_rootCert.GetSerialNumber() : null)); @@ -262,7 +274,7 @@ public void AlternateRootCertificateWithAuthorityKeyID(bool subjectKeyIdentifier var ski = m_rootAltCert.Extensions.OfType().Single(); // create a certificate with special key info / no key id - X509Extension authorityKeyIdentifier = new X509AuthorityKeyIdentifierExtension( + var authorityKeyIdentifier = new Ua.Security.Certificates.X509AuthorityKeyIdentifierExtension( (byte[])(subjectKeyIdentifier ? ski.SubjectKeyIdentifier.FromHexString() : null), (X500DistinguishedName)(issuerName ? m_rootAltCert.IssuerName : null), (byte[])(serialNumber ? m_rootAltCert.GetSerialNumber() : null)); diff --git a/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateValidatorTest.cs b/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateValidatorTest.cs index 4394d5241..a8da19854 100644 --- a/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateValidatorTest.cs +++ b/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateValidatorTest.cs @@ -39,7 +39,7 @@ using System.Threading.Tasks; using NUnit.Framework; using Opc.Ua.Security.Certificates; -#if NETCOREAPP2_1 +#if NETCOREAPP2_1 || !ECC_SUPPORT using X509SignatureGenerator = Opc.Ua.Security.Certificates.X509SignatureGenerator; #endif @@ -771,13 +771,13 @@ public void VerifyPemWriterPublicKeys() var pemDataBlob = PEMWriter.ExportCertificateAsPEM(appCert); var pemString = Encoding.UTF8.GetString(pemDataBlob); TestContext.Out.WriteLine(pemString); -#if NETCOREAPP3_1_OR_GREATER +#if NETCOREAPP3_1_OR_GREATER && ECC_SUPPORT var exception = Assert.Throws(() => CertificateFactory.CreateCertificateWithPEMPrivateKey(new X509Certificate2(appCert), pemDataBlob)); #endif } } -#if NETCOREAPP3_1_OR_GREATER +#if NETCOREAPP3_1_OR_GREATER && ECC_SUPPORT /// /// Verify the PEM Writer, no password. /// diff --git a/Tests/Opc.Ua.Core.Tests/Security/Certificates/TemporaryCertValidator.cs b/Tests/Opc.Ua.Core.Tests/Security/Certificates/TemporaryCertValidator.cs index 4caa0b1f8..60a1eb23f 100644 --- a/Tests/Opc.Ua.Core.Tests/Security/Certificates/TemporaryCertValidator.cs +++ b/Tests/Opc.Ua.Core.Tests/Security/Certificates/TemporaryCertValidator.cs @@ -54,13 +54,13 @@ private TemporaryCertValidator(bool rejectedStore) // pki directory root for test runs. m_pkiRoot = Path.GetTempPath() + Path.GetRandomFileName() + Path.DirectorySeparatorChar; m_issuerStore = new DirectoryCertificateStore(); - m_issuerStore.Open(m_pkiRoot + "issuer"); + m_issuerStore.Open(m_pkiRoot + "issuer", true); m_trustedStore = new DirectoryCertificateStore(); - m_trustedStore.Open(m_pkiRoot + "trusted"); + m_trustedStore.Open(m_pkiRoot + "trusted", true); if (rejectedStore) { m_rejectedStore = new DirectoryCertificateStore(); - m_rejectedStore.Open(m_pkiRoot + "rejected"); + m_rejectedStore.Open(m_pkiRoot + "rejected", true); } } diff --git a/Tests/Opc.Ua.Core.Tests/Types/Bindings/BufferManager.cs b/Tests/Opc.Ua.Core.Tests/Types/Bindings/BufferManager.cs new file mode 100644 index 000000000..719b4d9bc --- /dev/null +++ b/Tests/Opc.Ua.Core.Tests/Types/Bindings/BufferManager.cs @@ -0,0 +1,252 @@ +/* ======================================================================== + * Copyright (c) 2005-2023 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.Buffers; +using System.Threading; +using BenchmarkDotNet.Attributes; +using NUnit.Framework; +using Opc.Ua.Bindings; + +namespace Opc.Ua.Core.Tests.Stack.Bindings +{ + [TestFixture, Category("BufferManager")] + [SetCulture("en-us"), SetUICulture("en-us")] + [MemoryDiagnoser] + [BenchmarkCategory("BufferManager")] + public class BufferManagerBenchmarks + { + //[Params(8192, 65535, 1024 * 1024 - 1)] + public int BufferSize { get; set; } = 65535; + + //[Params( /*8,*/ 64, 256, 1024)] + public int Allocations { get; set; } = 256; + + //[Params(4, 32, 256)] + public int BucketSize { get; set; } = 32; + + /// + /// Benchmark allocation with new. + /// + // [Benchmark(Baseline = true)] + [Test] + public void BuffersWithNew() + { + for (int i = 0; i < Allocations; i++) + { + m_bufferArray[i] = new byte[BufferSize + 1]; + m_bufferArray[i][i] = (byte)i; + } + for (int i = 0; i < Allocations; i++) + { + m_bufferArray[i] = null; + } + } + +#if NET6_0_OR_GREATER + /// + /// Benchmark allocation with new. + /// + [Benchmark] + [Test] + public void BuffersWithAllocateUninitializedArray() + { + for (int i = 0; i < Allocations; i++) + { + m_bufferArray[i] = GC.AllocateUninitializedArray(BufferSize+1); + m_bufferArray[i][i] = (byte)i; + } + for (int i = 0; i < Allocations; i++) + { + m_bufferArray[i] = null; + } + } +#endif + + /// + /// Benchmark Buffer allocation. + /// + [Benchmark] + [Test] + public void ArrayPoolCreateTooSmall() + { + for (int i = 0; i < Allocations; i++) + { + m_bufferArray[i] = m_arrayPoolTooSmall.Rent(BufferSize + 1); + m_bufferArray[i][i] = (byte)i; + } + foreach (var buffer in m_bufferArray) + { + m_arrayPoolTooSmall.Return(buffer); + } + } + + /// + /// Benchmark Buffer allocation. + /// + [Benchmark] + [Test] + public void ArrayPoolCreate() + { + for (int i = 0; i < Allocations; i++) + { + m_bufferArray[i] = (m_arrayPool.Rent(BufferSize + 1)); + m_bufferArray[i][i] = (byte)i; + } + foreach (var buffer in m_bufferArray) + { + m_arrayPool.Return(buffer); + } + } + /// + /// Benchmark Buffer allocation. + /// + [Benchmark] + [Test] + public void ArrayPoolShared() + { + for (int i = 0; i < Allocations; i++) + { + m_bufferArray[i] = m_arrayPoolShared.Rent(BufferSize + 1); + m_bufferArray[i][i] = (byte)i; + } + foreach (var buffer in m_bufferArray) + { + m_arrayPoolShared.Return(buffer); + } + } + + /// + /// Benchmark Buffer allocation. + /// + [Benchmark] + [Test] + public void BufferManager() + { + for (int i = 0; i < Allocations; i++) + { + m_bufferArray[i] = m_bufferManager.TakeBuffer(BufferSize, nameof(BufferManager)); + m_bufferArray[i][i] = (byte)i; + } + foreach (var buffer in m_bufferArray) + { + m_bufferManager.ReturnBuffer(buffer, nameof(BufferManager)); + } + } + + [Benchmark] + [Test] + public void ReadWriterLockSlim() + { + int readValue; + try + { + m_readerWriterLockSlim.EnterReadLock(); + readValue = maxBufferSize; + } + finally + { + m_readerWriterLockSlim.ExitReadLock(); + } + } + + [Benchmark] + [Test] + public void Lock() + { + int readValue; + lock(m_lock) + { + readValue = maxBufferSize; + } + } + + #region Private Methods + #endregion + + #region Test Setup + [OneTimeSetUp] + public void OneTimeSetUp() + { + m_bufferArray = new byte[Allocations][]; + m_arrayPoolTooSmall = ArrayPool.Create(BufferSize, BucketSize); + m_arrayPool = ArrayPool.Create(BufferSize + 1, BucketSize); + m_arrayPoolShared = ArrayPool.Shared; + m_bufferManager = new BufferManager(nameof(BufferManager), BufferSize); + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + m_arrayPoolTooSmall = null; + m_arrayPool = null; + m_arrayPoolShared = null; + m_bufferManager = null; + } + #endregion + + #region Benchmark Setup + /// + /// Set up some variables for benchmarks. + /// + [GlobalSetup] + public void GlobalSetup() + { + m_bufferArray = new byte[Allocations][]; + m_arrayPoolTooSmall = ArrayPool.Create(BufferSize, BucketSize); + m_arrayPool = ArrayPool.Create(BufferSize + 1, BucketSize); + m_arrayPoolShared = ArrayPool.Shared; + m_bufferManager = new BufferManager(nameof(BufferManager), BufferSize); + } + + /// + /// Tear down benchmark variables. + /// + [GlobalCleanup] + public void GlobalCleanup() + { + m_arrayPoolTooSmall = null; + m_arrayPool = null; + m_arrayPoolShared = null; + m_bufferManager = null; + } + #endregion + + #region Private Fields + byte[][] m_bufferArray; + ArrayPool m_arrayPoolTooSmall; + ArrayPool m_arrayPool; + ArrayPool m_arrayPoolShared; + BufferManager m_bufferManager; + int maxBufferSize = 1234; + ReaderWriterLockSlim m_readerWriterLockSlim = new ReaderWriterLockSlim(); + readonly object m_lock = new object(); + #endregion + } +} diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/EncodeableTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/EncodeableTests.cs index 96f411f19..1f58d0afb 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/EncodeableTests.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/EncodeableTests.cs @@ -99,7 +99,9 @@ Type systemType { using (IEncoder encoder = CreateEncoder(encoderType, Context, encoderStream, systemType)) { + encoder.PushNamespace("urn:This:is:another:namespace"); encoder.WriteArray(objectName, array, ValueRanks.OneDimension, builtInType); + encoder.PopNamespace(); } buffer = encoderStream.ToArray(); } @@ -109,13 +111,19 @@ Type systemType case EncodingType.Json: PrettifyAndValidateJson(Encoding.UTF8.GetString(buffer)); break; + case EncodingType.Xml: + var xml = Encoding.UTF8.GetString(buffer); + Assert.IsTrue(xml.Contains("")); + break; } object result; using (var decoderStream = new MemoryStream(buffer)) using (IDecoder decoder = CreateDecoder(encoderType, Context, decoderStream, systemType)) { + decoder.PushNamespace("urn:This:is:another:namespace"); result = decoder.ReadArray(objectName, ValueRanks.OneDimension, BuiltInType.Variant, systemType, dataTypeId); + decoder.PopNamespace(); } TestContext.Out.WriteLine("Result:"); diff --git a/Tests/Opc.Ua.Core.Tests/Types/Schemas/BinarySchemaWellKnownTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Schemas/BinarySchemaWellKnownTests.cs index 5a71e7bbd..fd9e59fac 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Schemas/BinarySchemaWellKnownTests.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Schemas/BinarySchemaWellKnownTests.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2018 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 @@ -11,7 +11,7 @@ * 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, @@ -65,14 +65,14 @@ public void LoadResources(string[] schemaData) /// Load and validate well known resource type dictionaries. /// [Theory] - public async Task ValidateResources(string[] schemaData) + public void ValidateResources(string[] schemaData) { var assembly = typeof(BinarySchemaValidator).GetTypeInfo().Assembly; var stream = assembly.GetManifestResourceStream(schemaData[1]); Assert.IsNotNull(stream); var schema = new BinarySchemaValidator(); Assert.IsNotNull(schema); - await schema.Validate(stream).ConfigureAwait(false); + schema.Validate(stream); Assert.IsNotNull(schema.Dictionary); Assert.AreEqual(schemaData[0], schema.Dictionary.TargetNamespace); } diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs index eb1995812..1b727bdc7 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs @@ -95,6 +95,7 @@ public async Task StartServer(bool clean, int basePort = -1) // get the DatabaseStorePath configuration parameter. GlobalDiscoveryServerConfiguration gdsConfiguration = Config.ParseExtension(); string databaseStorePath = Utils.ReplaceSpecialFolderNames(gdsConfiguration.DatabaseStorePath); + string usersDatabaseStorePath = Utils.ReplaceSpecialFolderNames(gdsConfiguration.UsersDatabaseStorePath); if (clean) { @@ -103,6 +104,10 @@ public async Task StartServer(bool clean, int basePort = -1) { File.Delete(databaseStorePath); } + if (File.Exists(usersDatabaseStorePath)) + { + File.Delete(usersDatabaseStorePath); + } // clean up GDS stores TestUtils.DeleteDirectory(gdsConfiguration.AuthoritiesStorePath); @@ -113,13 +118,15 @@ public async Task StartServer(bool clean, int basePort = -1) } } - var database = JsonApplicationsDatabase.Load(databaseStorePath); + var applicationsDatabase = JsonApplicationsDatabase.Load(databaseStorePath); + var usersDatabase = JsonUsersDatabase.Load(usersDatabaseStorePath); // start the server. m_server = new GlobalDiscoverySampleServer( - database, - database, - new CertificateGroup()); + applicationsDatabase, + applicationsDatabase, + new CertificateGroup(), + usersDatabase); await Application.Start(m_server).ConfigureAwait(false); ServerState serverState = Server.GetStatus().State; @@ -208,7 +215,8 @@ private static async Task Load(ApplicationInstance app CACertificateLifetime = 60 } }, - DatabaseStorePath = Path.Combine(gdsRoot, "gdsdb.json") + DatabaseStorePath = Path.Combine(gdsRoot, "gdsdb.json"), + UsersDatabaseStorePath = Path.Combine(gdsRoot, "gdsusersdb.json") }; // build the application configuration. diff --git a/Tests/Opc.Ua.Gds.Tests/LinqUsersDatabaseTests.cs b/Tests/Opc.Ua.Gds.Tests/LinqUsersDatabaseTests.cs new file mode 100644 index 000000000..6e29de6d8 --- /dev/null +++ b/Tests/Opc.Ua.Gds.Tests/LinqUsersDatabaseTests.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using Opc.Ua.Gds.Server.Database.Linq; + +namespace Opc.Ua.Gds.Tests +{ + [TestFixture, Category("GDS")] + [SetCulture("en-us"), SetUICulture("en-us")] + [Parallelizable] + internal class LinqUsersDatabaseTests + { + #region Test Methods + + [Test] + public void CreateInvalidUser() + { + //Arrrange + var usersDb = new LinQUsersDatabase(); + + //Act+ Assert + Assert.Throws( + () => usersDb.CreateUser("", "PW", Server.GdsRole.ApplicationAdmin)); + Assert.Throws( + () => usersDb.CreateUser("Name", "", Server.GdsRole.ApplicationAdmin)); + } + + [Test] + public void DeleteExistingUser() + { + //Arrrange + var usersDb = new LinQUsersDatabase(); + usersDb.CreateUser("TestUser", "PW", Server.GdsRole.ApplicationAdmin); + //Act + var result = usersDb.DeleteUser("TestUser"); + //Assert + Assert.True(result); + } + + [Test] + public void DeleteNonExistingUser() + { + //Arrrange + var usersDb = new LinQUsersDatabase(); + usersDb.CreateUser("TestUser", "PW", Server.GdsRole.ApplicationAdmin); + //Act + var result = usersDb.DeleteUser("NoTestUser"); + //Assert + Assert.False(result); + } + + [Test] + public void ChangePwOfExistingUser() + { + //Arrrange + var usersDb = new LinQUsersDatabase(); + usersDb.CreateUser("TestUser", "PW", Server.GdsRole.ApplicationAdmin); + //Act + var result = usersDb.ChangePassword("TestUser", "PW", "newPW"); + var login = usersDb.CheckCredentials("TestUser", "newPW"); + var loginOldPW = usersDb.CheckCredentials("TestUser", "PW"); + //Assert + Assert.True(result); + Assert.True(login); + Assert.False(loginOldPW); + } + + [Test] + public void ChangePwOfNonExistingUser() + { + //Arrrange + var usersDb = new LinQUsersDatabase(); + usersDb.CreateUser("TestUser", "PW", Server.GdsRole.ApplicationAdmin); + //Act + var result = usersDb.DeleteUser("NoTestUser"); + //Assert + Assert.False(result); + } + + [Test] + public void CheckPWofExistingUser() + { + //Arrrange + var usersDb = new LinQUsersDatabase(); + usersDb.CreateUser("TestUser", "PW", Server.GdsRole.ApplicationAdmin); + //Act + var result = usersDb.CheckCredentials("TestUser", "PW"); + var loginWrongPw = usersDb.CheckCredentials("TestUser", "newPW"); + //Assert + Assert.True(result); + Assert.False(loginWrongPw); + } + + [Test] + public void CheckPWofNonExistingUser() + { + //Arrrange + var usersDb = new LinQUsersDatabase(); + usersDb.CreateUser("TestUser", "PW", Server.GdsRole.ApplicationAdmin); + //Act + var result = usersDb.CheckCredentials("NoTestUser", "PW"); + //Assert + Assert.False(result); + } + #endregion + } +} diff --git a/Tests/Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj b/Tests/Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj index 4982627c6..71ea65fb6 100644 --- a/Tests/Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj +++ b/Tests/Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj @@ -12,8 +12,8 @@ - - + + all diff --git a/Tests/Opc.Ua.Gds.Tests/Opc.Ua.GlobalDiscoveryTestServer.Config.xml b/Tests/Opc.Ua.Gds.Tests/Opc.Ua.GlobalDiscoveryTestServer.Config.xml index 4da22b8dc..ebf971a60 100644 --- a/Tests/Opc.Ua.Gds.Tests/Opc.Ua.GlobalDiscoveryTestServer.Config.xml +++ b/Tests/Opc.Ua.Gds.Tests/Opc.Ua.GlobalDiscoveryTestServer.Config.xml @@ -149,6 +149,7 @@ %LocalApplicationData%/OPC/GDS/gdsdb.json + %LocalApplicationData%/OPC/GDS/gdsusersdb.json diff --git a/Tests/Opc.Ua.PubSub.Tests/Configuration/PubSubStateMachineTests.Publisher.cs b/Tests/Opc.Ua.PubSub.Tests/Configuration/PubSubStateMachineTests.Publisher.cs index 8d4e6e61c..407232bfb 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Configuration/PubSubStateMachineTests.Publisher.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Configuration/PubSubStateMachineTests.Publisher.cs @@ -72,7 +72,7 @@ public void ValidateDisabled_0ToPause_1_Publisher() PubSubState conState = uaPubSubApplication.UaPubSubConfigurator.FindStateForObject(publisherConnection); PubSubState wgState = uaPubSubApplication.UaPubSubConfigurator.FindStateForObject(writerGroup); PubSubState dswState = uaPubSubApplication.UaPubSubConfigurator.FindStateForObject(datasetWriter); - Assert.That(psState == PubSubState.Disabled, Is.True); + Assert.That(psState == PubSubState.Disabled, Is.True); Assert.That(conState == PubSubState.Disabled, Is.True); Assert.That(wgState == PubSubState.Disabled, Is.True); Assert.That(dswState == PubSubState.Disabled, Is.True); diff --git a/Tests/Opc.Ua.PubSub.Tests/Configuration/UaPubSubApplicationTests.cs b/Tests/Opc.Ua.PubSub.Tests/Configuration/UaPubSubApplicationTests.cs index 55ba276ac..46394057d 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Configuration/UaPubSubApplicationTests.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Configuration/UaPubSubApplicationTests.cs @@ -74,9 +74,9 @@ public void ValidateUaPubSubApplicationCreate() Assert.IsTrue(connection.Publishers != null, "connection.Publishers is null"); Assert.IsTrue(connection.Publishers.Count == 1, "connection.Publishers count is not 2"); int index = 0; - foreach(IUaPublisher publisher in connection.Publishers) + foreach (IUaPublisher publisher in connection.Publishers) { - Assert.IsTrue(publisher!= null, "connection.Publishers[{0}] is null", index); + Assert.IsTrue(publisher != null, "connection.Publishers[{0}] is null", index); Assert.IsTrue(publisher.PubSubConnection == connection, "connection.Publishers[{0}].PubSubConnection is not set correctly", index); Assert.IsTrue(publisher.WriterGroupConfiguration.WriterGroupId == m_pubSubConfiguration.Connections.First().WriterGroups[index].WriterGroupId, "connection.Publishers[{0}].WriterGroupConfiguration is not set correctly", index); index++; diff --git a/Tests/Opc.Ua.PubSub.Tests/Configuration/UaPublisherTests.cs b/Tests/Opc.Ua.PubSub.Tests/Configuration/UaPublisherTests.cs index 8d8e690b4..aad156fd8 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Configuration/UaPublisherTests.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Configuration/UaPublisherTests.cs @@ -36,19 +36,19 @@ namespace Opc.Ua.PubSub.Tests.Configuration { - [TestFixture(Description ="Tests for UAPublisher class")] + [TestFixture(Description = "Tests for UAPublisher class")] public class UaPublisherTests { static IList s_publishTimes = new List(); - [Test(Description ="Test that PublishMessage method is called after a UAPublisher is started.")] + [Test(Description = "Test that PublishMessage method is called after a UAPublisher is started.")] [Combinatorial] #if !CUSTOM_TESTS [Ignore("This test should be executed locally")] #endif public void ValidateUaPublisherPublishIntervalDeviation( [Values(100, 1000, 2000)] double publishingInterval, - [Values(30, 40)]double maxDeviation, + [Values(30, 40)] double maxDeviation, [Values(10)] int publishTimeInSeconds) { //Arrange @@ -57,24 +57,24 @@ public void ValidateUaPublisherPublishIntervalDeviation( mockConnection.Setup(x => x.CanPublish(It.IsAny())).Returns(true); mockConnection.Setup(x => x.CreateNetworkMessages(It.IsAny(), It.IsAny())) - .Callback(()=>s_publishTimes.Add(DateTime.Now)); + .Callback(() => s_publishTimes.Add(DateTime.Now)); WriterGroupDataType writerGroupDataType = new WriterGroupDataType(); writerGroupDataType.PublishingInterval = publishingInterval; - + //Act UaPublisher publisher = new UaPublisher(mockConnection.Object, writerGroupDataType); publisher.Start(); //wait so many seconds - Thread.Sleep(publishTimeInSeconds*1000); + Thread.Sleep(publishTimeInSeconds * 1000); publisher.Stop(); int faultIndex = -1; double faultDeviation = 0; - s_publishTimes = (from t in s_publishTimes - orderby t - select t).ToList(); + s_publishTimes = (from t in s_publishTimes + orderby t + select t).ToList(); //Assert for (int i = 1; i < s_publishTimes.Count; i++) diff --git a/Tests/Opc.Ua.PubSub.Tests/Encoding/MessagesHelper.cs b/Tests/Opc.Ua.PubSub.Tests/Encoding/MessagesHelper.cs index aacf29183..bf11e5fdc 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Encoding/MessagesHelper.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Encoding/MessagesHelper.cs @@ -436,9 +436,7 @@ public static PubSubConfigurationDataType CreateAzurePublisherConfiguration( { foreach (var writerGroup in pubSubConnection.WriterGroups) { - BrokerWriterGroupTransportDataType brokerTransportSettings = ExtensionObject.ToEncodeable(writerGroup.TransportSettings) - as BrokerWriterGroupTransportDataType; - if (brokerTransportSettings != null) + if (ExtensionObject.ToEncodeable(writerGroup.TransportSettings) is BrokerWriterGroupTransportDataType brokerTransportSettings) { brokerTransportSettings.QueueName = topic; } @@ -762,7 +760,7 @@ public static ReaderGroupDataType CreateReaderGroup(ushort readerGroupId, ReaderGroupTransportDataType transportSettings) { ReaderGroupDataType readerGroup = new ReaderGroupDataType(); - readerGroup.Name = $"ReaderGroup { readerGroupId}"; + readerGroup.Name = $"ReaderGroup {readerGroupId}"; readerGroup.Enabled = true; readerGroup.MaxNetworkMessageSize = 1500; readerGroup.MessageSettings = new ExtensionObject(messageSettings); @@ -780,7 +778,7 @@ public static ReaderGroupDataType GetReaderGroup(PubSubConnectionDataType connec { if (connection != null) { - return connection.ReaderGroups.Find(x => x.Name == $"ReaderGroup { writerGroupId}"); + return connection.ReaderGroups.Find(x => x.Name == $"ReaderGroup {writerGroupId}"); } return null; } @@ -1096,9 +1094,7 @@ public static PubSubConfigurationDataType CreateAzureSubscriberConfiguration( { foreach (var dataSetReader in readerGroup.DataSetReaders) { - BrokerDataSetReaderTransportDataType brokerTransportSettings = ExtensionObject.ToEncodeable(dataSetReader.TransportSettings) - as BrokerDataSetReaderTransportDataType; - if (brokerTransportSettings != null) + if (ExtensionObject.ToEncodeable(dataSetReader.TransportSettings) is BrokerDataSetReaderTransportDataType brokerTransportSettings) { brokerTransportSettings.QueueName = topic; } diff --git a/Tests/Opc.Ua.PubSub.Tests/Encoding/MqttUadpNetworkMessageTests.cs b/Tests/Opc.Ua.PubSub.Tests/Encoding/MqttUadpNetworkMessageTests.cs index 71f8dbc02..969b24caf 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Encoding/MqttUadpNetworkMessageTests.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Encoding/MqttUadpNetworkMessageTests.cs @@ -156,7 +156,7 @@ public void ValidatePublisherIdWithWithPublisherIdParameter( )] object publisherId) { // Arrange - UadpNetworkMessageContentMask uadpNetworkMessageContentMask = UadpNetworkMessageContentMask.PublisherId | UadpNetworkMessageContentMask.WriterGroupId; + UadpNetworkMessageContentMask uadpNetworkMessageContentMask = UadpNetworkMessageContentMask.PublisherId | UadpNetworkMessageContentMask.WriterGroupId; UadpDataSetMessageContentMask uadpDataSetMessageContentMask = UadpDataSetMessageContentMask.SequenceNumber; @@ -881,8 +881,8 @@ public void ValidateDataSetClassIdWithPublisherIdParameter( // filter out the metadata message networkMessages = (from m in networkMessages - where !m.IsMetaDataMessage - select m).ToList(); + where !m.IsMetaDataMessage + select m).ToList(); Assert.IsNotNull(networkMessages, "connection.CreateNetworkMessages shall not return null"); Assert.AreEqual(1, networkMessages.Count, "connection.CreateNetworkMessages shall return only one network message"); @@ -926,7 +926,7 @@ public void ValidateMetaDataIsEncodedCorrectly( // Arrange UadpNetworkMessageContentMask uadpNetworkMessageContentMask = UadpNetworkMessageContentMask.PublisherId; UadpDataSetMessageContentMask uadpDataSetMessageContentMask = UadpDataSetMessageContentMask.None; - + DataSetMetaDataType[] dataSetMetaDataArray = new DataSetMetaDataType[] { MessagesHelper.CreateDataSetMetaData1("MetaData1"), @@ -983,7 +983,7 @@ public void ValidateMetaDataUpdateTimeZeroSentAtStartup( // Arrange UadpNetworkMessageContentMask uadpNetworkMessageContentMask = UadpNetworkMessageContentMask.PublisherId; UadpDataSetMessageContentMask uadpDataSetMessageContentMask = UadpDataSetMessageContentMask.None; - + DataSetMetaDataType[] dataSetMetaDataArray = new DataSetMetaDataType[] { MessagesHelper.CreateDataSetMetaData1("MetaData1"), @@ -1328,7 +1328,7 @@ private void Compare(UadpNetworkMessage uadpNetworkMessageEncode, UadpNetworkMes { // check the number of UadpDataSetMessage counts Assert.AreEqual(uadpNetworkMessageEncode.DataSetMessages.Count, - uadpNetworkMessageDecoded.DataSetMessages.Count, "UadpDataSetMessages.Count was not decoded correctly"); + uadpNetworkMessageDecoded.DataSetMessages.Count, "UadpDataSetMessages.Count was not decoded correctly"); } #endregion @@ -1443,7 +1443,7 @@ private void Compare(UadpNetworkMessage uadpNetworkMessageEncode, UadpNetworkMes Assert.AreEqual(dataValueEncoded.ServerPicoseconds, dataValueDecoded.ServerPicoseconds, "Wrong: Fields[{0}].DataValue.ServerPicoseconds; DataSetWriterId = {1}", fieldName, uadpDataSetMessage.DataSetWriterId); } - } + } } #endregion } diff --git a/Tests/Opc.Ua.PubSub.Tests/Encoding/UadpDataSetMessageTests.cs b/Tests/Opc.Ua.PubSub.Tests/Encoding/UadpDataSetMessageTests.cs index 58692ac64..da48270b6 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Encoding/UadpDataSetMessageTests.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Encoding/UadpDataSetMessageTests.cs @@ -51,7 +51,7 @@ public class UadpDataSetMessageTests private UaPubSubApplication m_subscriberApplication; private ReaderGroupDataType m_firstReaderGroup; private DataSetReaderDataType m_firstDataSetReaderType; - + private const ushort kNamespaceIndexSimple = 2; /// @@ -78,7 +78,7 @@ public void MyTestInitialize() Assert.IsNotEmpty(m_publisherConfiguration.Connections, "m_publisherConfiguration.Connections should not be empty"); m_firstPublisherConnection = m_publisherApplication.PubSubConnections[0]; Assert.IsNotNull(m_firstPublisherConnection, "m_firstPublisherConnection should not be null"); - + // Read the first writer group Assert.IsNotEmpty(m_publisherConfiguration.Connections[0].WriterGroups, "pubSubConfigConnection.WriterGroups should not be empty"); m_firstWriterGroup = m_publisherConfiguration.Connections[0].WriterGroups[0]; @@ -606,7 +606,7 @@ private void CompareUadpDataSetMessages(UadpDataSetMessage uadpDataSetMessageEnc "DataSetMessages DataSetFlags1 do not match:"); Assert.AreEqual(uadpDataSetMessageEncode.DataSetFlags2, uadpDataSetMessageDecoded.DataSetFlags2, "DataSetMessages DataSetFlags2 do not match:"); - + if ((dataSetMessageContentMask & UadpDataSetMessageContentMask.Timestamp) == UadpDataSetMessageContentMask.Timestamp) { diff --git a/Tests/Opc.Ua.PubSub.Tests/Encoding/UadpNetworkMessageTests.cs b/Tests/Opc.Ua.PubSub.Tests/Encoding/UadpNetworkMessageTests.cs index 384b4d678..364869e3c 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Encoding/UadpNetworkMessageTests.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Encoding/UadpNetworkMessageTests.cs @@ -130,9 +130,9 @@ public void ValidatePublisherId( CompareEncodeDecode(uaNetworkMessage); } - [Test(Description = "Invalidate PublisherId with wrong data type")] + [Test(Description = "Invalidate PublisherId with wrong data type")] public void InvalidatePublisherId([ - Values(DataSetFieldContentMask.None, DataSetFieldContentMask.RawData, // list here all possible DataSetFieldContentMask + Values(DataSetFieldContentMask.None, DataSetFieldContentMask.RawData, // list here all possible DataSetFieldContentMask DataSetFieldContentMask.ServerPicoSeconds, DataSetFieldContentMask.ServerTimestamp, DataSetFieldContentMask.SourcePicoSeconds, DataSetFieldContentMask.SourceTimestamp, DataSetFieldContentMask.StatusCode, DataSetFieldContentMask.ServerPicoSeconds| DataSetFieldContentMask.ServerTimestamp, @@ -147,7 +147,7 @@ public void InvalidatePublisherId([ DataSetFieldContentMask.ServerPicoSeconds| DataSetFieldContentMask.ServerTimestamp| DataSetFieldContentMask.SourcePicoSeconds| DataSetFieldContentMask.SourceTimestamp| DataSetFieldContentMask.StatusCode )] DataSetFieldContentMask dataSetFieldContentMask, - [Values((float)10, (double)10)] object publisherId) + [Values((float)10, (double)10)] object publisherId) { // Arrange UadpNetworkMessage uaNetworkMessage = CreateNetworkMessage(dataSetFieldContentMask); @@ -192,7 +192,7 @@ public void ValidateGroupHeader( CompareEncodeDecode(uaNetworkMessage); } - + [Test(Description = "Validate WriterGroupId")] public void ValidateWriterGroupIdWithVariantType( @@ -529,7 +529,7 @@ private UadpNetworkMessage CreateNetworkMessage(DataSetFieldContentMask dataSetF // The Variant can contain a StatusCode instead of the expected DataType if the status of the field is Bad. // The Variant can contain a DataValue with the value and the statusCode if the status of the field is Uncertain. dataSetWriter.DataSetFieldContentMask = (uint)dataSetFieldContentMask; - } + } var networkMessages = m_firstPublisherConnection.CreateNetworkMessages(m_firstWriterGroup, new WriterGroupPublishState()); // filter out the metadata message @@ -556,7 +556,7 @@ private void CompareEncodeDecode(UadpNetworkMessage uadpNetworkMessage) byte[] bytes = uadpNetworkMessage.Encode(ServiceMessageContext.GlobalContext); UadpNetworkMessage uaNetworkMessageDecoded = new UadpNetworkMessage(); - uaNetworkMessageDecoded.Decode(new ServiceMessageContext(), bytes, m_firstDataSetReadersType); + uaNetworkMessageDecoded.Decode(new ServiceMessageContext(), bytes, m_firstDataSetReadersType); // compare uaNetworkMessage with uaNetworkMessageDecoded Compare(uadpNetworkMessage, uaNetworkMessageDecoded); @@ -621,7 +621,7 @@ private void Compare(UadpNetworkMessage uadpNetworkMessageEncode, UadpNetworkMes // Verify flags Assert.AreEqual(uadpNetworkMessageEncode.UADPFlags, uadpNetworkMessageDecoded.UADPFlags, "UADPFlags were not decoded correctly"); - + #region Network Message Header if ((networkMessageContentMask & UadpNetworkMessageContentMask.PublisherId) != 0) { @@ -674,7 +674,7 @@ private void Compare(UadpNetworkMessage uadpNetworkMessageEncode, UadpNetworkMes uadpNetworkMessageDecoded.DataSetMessages.Count, "UadpDataSetMessages.Count was not decoded correctly"); // check if the encoded match the decoded DataSetWriterId's - + foreach (UadpDataSetMessage uadpDataSetMessage in uadpNetworkMessageEncode.DataSetMessages) { UadpDataSetMessage uadpDataSetMessageDecoded = diff --git a/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj b/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj index 772e56873..a29903ade 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj +++ b/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj @@ -8,20 +8,20 @@ - - + + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Tests/Opc.Ua.PubSub.Tests/PublishedData/DataCollectorTests.cs b/Tests/Opc.Ua.PubSub.Tests/PublishedData/DataCollectorTests.cs index cbc499673..7966bc608 100644 --- a/Tests/Opc.Ua.PubSub.Tests/PublishedData/DataCollectorTests.cs +++ b/Tests/Opc.Ua.PubSub.Tests/PublishedData/DataCollectorTests.cs @@ -48,7 +48,7 @@ public void ValidateAddPublishedDataSetWithNullParameter() { //Arrange DataCollector dataCollector = new DataCollector(new UaPubSubDataStore()); - + //Assert Assert.Throws(() => dataCollector.AddPublishedDataSet(null)); } @@ -65,7 +65,7 @@ public void ValidateAddPublishedDataSet() dataCollector.AddPublishedDataSet(pubSubConfiguration.PublishedDataSets.First()); DataSet collectedDataSet = dataCollector.CollectData(pubSubConfiguration.PublishedDataSets.First().Name); //Assert - Assert.IsNotNull(collectedDataSet, + Assert.IsNotNull(collectedDataSet, "Cannot collect data therefore the '{0}' publishedDataSet was not registered correctly.", pubSubConfiguration.PublishedDataSets[0].Name); } @@ -141,7 +141,7 @@ public void ValidateCollectDataFromDataStore() DataType = DataTypeIds.DateTime, ValueRank = ValueRanks.Scalar } - }; + }; PublishedDataItemsDataType publishedDataItems = new PublishedDataItemsDataType(); publishedDataItems.PublishedData = new PublishedVariableDataTypeCollection(); @@ -149,8 +149,7 @@ public void ValidateCollectDataFromDataStore() foreach (var field in publishedDataSetSimple.DataSetMetaData.Fields) { publishedDataItems.PublishedData.Add( - new PublishedVariableDataType() - { + new PublishedVariableDataType() { PublishedVariable = new NodeId(field.Name, NamespaceIndex), AttributeId = Attributes.Value, }); @@ -168,7 +167,7 @@ public void ValidateCollectDataFromDataStore() Assert.IsNotNull(collectedDataSet.Fields, "collectedDataSet.Fields is null."); Assert.AreEqual(collectedDataSet.Fields.Length, publishedDataItems.PublishedData.Count, "collectedDataSet and published data fields count do not match."); - + // validate collected values Assert.AreEqual(collectedDataSet.Fields[0].Value.Value, false, "collectedDataSet.Fields[0].Value.Value does not match."); Assert.AreEqual(collectedDataSet.Fields[1].Value.Value, (int)1, "collectedDataSet.Fields[1].Value.Value does not match."); @@ -253,8 +252,7 @@ public void ValidateCollectDataFromExtensionFields() foreach (var field in publishedDataSetSimple.DataSetMetaData.Fields) { publishedDataItems.PublishedData.Add( - new PublishedVariableDataType() - { + new PublishedVariableDataType() { SubstituteValue = new QualifiedName(field.Name) }); } @@ -293,9 +291,9 @@ public void ValidateCollectDataNullDataSetName() { //Arrange DataCollector dataCollector = new DataCollector(new UaPubSubDataStore()); - + //Assert - Assert.Throws(()=> dataCollector.CollectData(null), "The data collect does not throw exception when null parameter."); + Assert.Throws(() => dataCollector.CollectData(null), "The data collect does not throw exception when null parameter."); } } } diff --git a/Tests/Opc.Ua.PubSub.Tests/Transport/MqttPubSubConnectionTests.cs b/Tests/Opc.Ua.PubSub.Tests/Transport/MqttPubSubConnectionTests.cs index 4e70545e3..ef466f7d4 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Transport/MqttPubSubConnectionTests.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Transport/MqttPubSubConnectionTests.cs @@ -148,7 +148,7 @@ public void ValidateMqttLocalPubSubConnectionWithUadp( //Act // it will signal if the uadp message was received from local ip m_uaDataShutdownEvent = new ManualResetEvent(false); - + subscriberApplication.DataReceived += UaPubSubApplication_DataReceived; subscriberConnection.Start(); @@ -159,10 +159,10 @@ public void ValidateMqttLocalPubSubConnectionWithUadp( { Assert.Fail("The UADP message was not received"); } - + subscriberConnection.Stop(); publisherConnection.Stop(); - + } [Test(Description = "Validate mqtt local pub/sub connection with json data.")] diff --git a/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.Publisher.cs b/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.Publisher.cs index 432db1017..34f0d448e 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.Publisher.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.Publisher.cs @@ -28,8 +28,6 @@ * ======================================================================*/ using NUnit.Framework; -using Opc.Ua; -using Opc.Ua.PubSub; using Opc.Ua.PubSub.Configuration; using Opc.Ua.PubSub.Transport; using System; @@ -271,11 +269,11 @@ public void ValidateUdpPubSubConnectionNetworkMessagePublishMulticast() } } - [Test(Description = "Validate discovery request PublishNetworkMessage"), Order(4)] + [Test(Description = "Validate discovery request PublishNetworkMessage for a DataSetMetaData"), Order(4)] #if !CUSTOM_TESTS [Ignore("A network interface controller is necessary in order to run correctly.")] #endif - public void ValidateUdpPubSubConnectionNetworkMessageDiscoveryPublish() + public void ValidateUdpPubSubConnectionNetworkMessageDiscoveryPublish_DataSetMetadata() { //Arrange var localhost = GetFirstNic(); @@ -352,6 +350,172 @@ public void ValidateUdpPubSubConnectionNetworkMessageDiscoveryPublish() } } + + [Test(Description = "Validate discovery DataSetWriterConfigurationMessage response"), Order(4)] +#if !CUSTOM_TESTS + [Ignore("A network interface controller is necessary in order to run correctly.")] +#endif + public void ValidateUdpPubSubConnectionNetworkMessageDiscoveryPublish_DataSetWriterConfiguration() + { + //Arrange + var localhost = GetFirstNic(); + Assert.IsNotNull(localhost, "localhost is null"); + Assert.IsNotNull(localhost.Address, "localhost.Address is null"); + + //create publisher configuration object with modified port + string configurationFile = Utils.GetAbsoluteFilePath(m_publisherConfigurationFileName, true, true, false); + PubSubConfigurationDataType publisherConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(publisherConfiguration, "publisherConfiguration is null"); + Assert.Greater(publisherConfiguration.Connections.Count, 1, "publisherConfiguration.Connection should be > 0"); + + //discovery IP adress 224.0.2.14 + IPAddress[] multicastIPAddresses = Dns.GetHostAddresses(kUdpDiscoveryIp); + IPAddress multicastIPAddress = multicastIPAddresses.First(); + Assert.IsNotNull(multicastIPAddress, "multicastIPAddress is null"); + + NetworkAddressUrlDataType publisherAddress = new NetworkAddressUrlDataType(); + publisherAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + publisherConfiguration.Connections[0].Address = new ExtensionObject(publisherAddress); + + //create publisher UaPubSubApplication with changed configuration settings + UaPubSubApplication publisherApplication = UaPubSubApplication.Create(publisherConfiguration); + Assert.IsNotNull(publisherApplication, "publisherApplication is null"); + + UdpPubSubConnection publisherConnection = publisherApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(publisherConnection, "publisherConnection is null"); + + // will signal that the uadp message was received from local ip + m_shutdownEvent = new ManualResetEvent(false); + + //setup uadp client for receiving from multicast (simulate a subscriber multicast) + UdpClient udpMulticastClient = new UdpClientMulticast(localhost.Address, multicastIPAddress, kDiscoveryPortNo); + udpMulticastClient.BeginReceive(new AsyncCallback(OnReceive), udpMulticastClient); + + // prepare a network message + WriterGroupDataType writerGroup0 = publisherConnection.PubSubConnectionConfiguration.WriterGroups.First(); + List dataSetWriterIds = new List(); + foreach (DataSetWriterDataType dataSetWriterDataType in writerGroup0.DataSetWriters) + { + dataSetWriterIds.Add(dataSetWriterDataType.DataSetWriterId); + } + UaNetworkMessage networkMessage = publisherConnection.CreateDataSetWriterCofigurationMessage(dataSetWriterIds.ToArray()).First(); + Assert.IsNotNull(networkMessage, "connection.CreateDataSetWriterCofigurationMessages shall not return null"); + + //Act + publisherConnection.Start(); + + if (networkMessage != null) + { + publisherConnection.PublishNetworkMessage(networkMessage); + } + + //Assert + bool noMessageReceived = false; + if (!m_shutdownEvent.WaitOne(kEstimatedPublishingTime)) + { + noMessageReceived = true; + } + + publisherConnection.Stop(); + udpMulticastClient.Close(); + udpMulticastClient.Dispose(); + + if (noMessageReceived) + { + Assert.Fail("The UDP message was not received"); + } + } + + [Test(Description = "Validate discovery request PublishNetworkMessage for PublisherEndpoints"), Order(4)] +#if !CUSTOM_TESTS + [Ignore("A network interface controller is necessary in order to run correctly.")] +#endif + public void ValidateUdpPubSubConnectionNetworkMessageDiscoveryPublish_PublisherEndpoints() + { + //Arrange + var localhost = GetFirstNic(); + Assert.IsNotNull(localhost, "localhost is null"); + Assert.IsNotNull(localhost.Address, "localhost.Address is null"); + + //create publisher configuration object with modified port + string configurationFile = Utils.GetAbsoluteFilePath(m_publisherConfigurationFileName, true, true, false); + PubSubConfigurationDataType publisherConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(publisherConfiguration, "publisherConfiguration is null"); + Assert.Greater(publisherConfiguration.Connections.Count, 1, "publisherConfiguration.Connection should be > 0"); + + //discovery IP adress 224.0.2.14 + IPAddress[] multicastIPAddresses = Dns.GetHostAddresses(kUdpDiscoveryIp); + IPAddress multicastIPAddress = multicastIPAddresses.First(); + Assert.IsNotNull(multicastIPAddress, "multicastIPAddress is null"); + + NetworkAddressUrlDataType publisherAddress = new NetworkAddressUrlDataType(); + publisherAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + publisherConfiguration.Connections[0].Address = new ExtensionObject(publisherAddress); + + //create publisher UaPubSubApplication with changed configuration settings + UaPubSubApplication publisherApplication = UaPubSubApplication.Create(publisherConfiguration); + Assert.IsNotNull(publisherApplication, "publisherApplication is null"); + + UdpPubSubConnection publisherConnection = publisherApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(publisherConnection, "publisherConnection is null"); + + // will signal that the uadp message was received from local ip + m_shutdownEvent = new ManualResetEvent(false); + + //setup uadp client for receiving from multicast (simulate a subscriber multicast) + UdpClient udpMulticastClient = new UdpClientMulticast(localhost.Address, multicastIPAddress, kDiscoveryPortNo); + udpMulticastClient.BeginReceive(new AsyncCallback(OnReceive), udpMulticastClient); + + List endpointDescriptions = new List() + { + new EndpointDescription() { + EndpointUrl = "opc.tcp://server1:4840/Test", + SecurityMode = MessageSecurityMode.None, + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#None", + Server = new ApplicationDescription() { ApplicationName = "Test security mode None", ApplicationUri = "urn:localhost:Server" } + }, + new EndpointDescription() + { + EndpointUrl = "opc.tcp://server1:4840/Test", + SecurityMode = MessageSecurityMode.Sign, + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256", + Server = new ApplicationDescription() { ApplicationName = "Test security mode Sign", ApplicationUri = "urn:localhost:Server" } + }, + new EndpointDescription() + { + EndpointUrl = "opc.tcp://server1:4840/Test", + SecurityMode = MessageSecurityMode.SignAndEncrypt, + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256", + Server = new ApplicationDescription() { ApplicationName = "Test security mode SignAndEncrypt", ApplicationUri = "urn:localhost:Server" } + } + }; + + UaNetworkMessage uaNetworkMessage = publisherConnection.CreatePublisherEndpointsNetworkMessage(endpointDescriptions.ToArray(), + StatusCodes.Good, publisherConnection.PubSubConnectionConfiguration.PublisherId.Value); + Assert.IsNotNull(uaNetworkMessage, "uaNetworkMessage shall not return null"); + + //Act + publisherConnection.Start(); + + publisherConnection.PublishNetworkMessage(uaNetworkMessage); + + // Assert + bool noMessageReceived = false; + if (!m_shutdownEvent.WaitOne(kEstimatedPublishingTime)) + { + noMessageReceived = true; + } + + publisherConnection.Stop(); + udpMulticastClient.Close(); + udpMulticastClient.Dispose(); + + if (noMessageReceived) + { + Assert.Fail("The UDP message was not received"); + } + } + /// /// Handle Receive event for an UADP channel /// diff --git a/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.Subscriber.cs b/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.Subscriber.cs index a2ba8696c..a9fb154d1 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.Subscriber.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.Subscriber.cs @@ -98,7 +98,7 @@ public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromUnicast() IPEndPoint remoteEndPoint = new IPEndPoint(localhost.Address, kDiscoveryPortNo); Assert.IsNotNull(remoteEndPoint, "remoteEndPoint is null"); - m_sentBytes = PrepareData(publisherConnection); + m_sentBytes = BuildNetworkMessages(publisherConnection); int sentBytesLen = udpUnicastClient.Send(m_sentBytes, m_sentBytes.Length, remoteEndPoint); Assert.AreEqual(sentBytesLen, m_sentBytes.Length, "Sent bytes size not equal to published bytes size!"); @@ -159,7 +159,7 @@ public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromBroadcast() //Act subscriberConnection.Start(); m_shutdownEvent = new ManualResetEvent(false); - m_sentBytes = PrepareData(publisherConnection); + m_sentBytes = BuildNetworkMessages(publisherConnection); // first physical network ip is mandatory on UdpClientBroadcast as parameter UdpClient udpBroadcastClient = new UdpClientBroadcast(localhost.Address, kDiscoveryPortNo, UsedInContext.Publisher); @@ -227,7 +227,7 @@ public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromMulticast() //Act subscriberConnection.Start(); m_shutdownEvent = new ManualResetEvent(false); - m_sentBytes = PrepareData(publisherConnection); + m_sentBytes = BuildNetworkMessages(publisherConnection); // first physical network ip is mandatory on UdpClientMulticast as parameter, for multicast publisher the port must not be 4840 UdpClient udpMulticastClient = new UdpClientMulticast(localhost.Address, multicastIPAddress, 0); @@ -254,7 +254,7 @@ public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromMulticast() #if !CUSTOM_TESTS [Ignore("A network interface controller is necessary in order to run correctly.")] #endif - public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryResponse() + public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryResponse_DataSetMetadata() { // Arrange var localhost = GetFirstNic(); @@ -265,10 +265,12 @@ public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryRespons IPAddress multicastIPAddress = new IPAddress(new byte[4] { 224, 0, 2, 14 }); Assert.IsNotNull(multicastIPAddress, "multicastIPAddress is null"); + //set subscriber configuration string configurationFile = Utils.GetAbsoluteFilePath(m_subscriberConfigurationFileName, true, true, false); PubSubConfigurationDataType subscriberConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); Assert.IsNotNull(subscriberConfiguration, "subscriberConfiguration is null"); + //set address and create subscriber NetworkAddressUrlDataType subscriberAddress = new NetworkAddressUrlDataType(); subscriberAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); subscriberConfiguration.Connections[0].Address = new ExtensionObject(subscriberAddress); @@ -278,12 +280,16 @@ public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryRespons UdpPubSubConnection subscriberConnection = subscriberApplication.PubSubConnections.First() as UdpPubSubConnection; Assert.IsNotNull(subscriberConnection, "subscriberConnection is null"); - subscriberApplication.RawDataReceived += RawDataReceived; + //subscribe to event handlers + subscriberApplication.RawDataReceived += RawDataReceived_NoRequests; + subscriberApplication.MetaDataReceived += MetaDataReceived; + //set publisher cofiguration configurationFile = Utils.GetAbsoluteFilePath(m_publisherConfigurationFileName, true, true, false); PubSubConfigurationDataType publisherConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); Assert.IsNotNull(publisherConfiguration, "publisherConfiguration is null"); + //set address and create publisher NetworkAddressUrlDataType publisherAddress = new NetworkAddressUrlDataType(); publisherAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); publisherConfiguration.Connections.First().Address = new ExtensionObject(publisherAddress); @@ -293,17 +299,33 @@ public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryRespons UdpPubSubConnection publisherConnection = publisherApplication.PubSubConnections.First() as UdpPubSubConnection; Assert.IsNotNull(publisherConnection, "publisherConnection is null"); - //Act + //start subcriber and prepare the message subscriberConnection.Start(); m_shutdownEvent = new ManualResetEvent(false); - m_sentBytes = PrepareData(publisherConnection, UdpConnectionType.Discovery); + m_sentBytes = BuildNetworkMessages(publisherConnection, UdpConnectionType.Discovery); + subscriberConnection.RequestDataSetMetaData(); + + //create multicast client // first physical network ip is mandatory on UdpClientMulticast as parameter, for multicast publisher the port must not be 4840 UdpClient udpMulticastClient = new UdpClientMulticast(localhost.Address, multicastIPAddress, 0); Assert.IsNotNull(udpMulticastClient, "udpMulticastClient is null"); + //set endpoint and send message IPEndPoint remoteEndPoint = new IPEndPoint(multicastIPAddress, kDiscoveryPortNo); int sentBytesLen = udpMulticastClient.Send(m_sentBytes, m_sentBytes.Length, remoteEndPoint); + + //manually create dataset metadata message and trigger metadata reveived event for test + DataSetMetaDataType metaData = m_uaPublisherApplication.DataCollector.GetPublishedDataSet(m_uaPublisherApplication.UaPubSubConfigurator.PubSubConfiguration.PublishedDataSets.First().Name)?.DataSetMetaData; + WriterGroupDataType writerConfig = m_uaPublisherApplication.PubSubConnections.First().PubSubConnectionConfiguration.WriterGroups.First(); + UadpNetworkMessage networkMessage = new UadpNetworkMessage(writerConfig, metaData) { PublisherId = m_uaPublisherApplication.ApplicationId, DataSetWriterId = writerConfig.DataSetWriters.First().DataSetWriterId }; + SubscribedDataEventArgs subscribedDataEventArgs = new SubscribedDataEventArgs() + { + NetworkMessage = networkMessage, + }; + subscriberApplication.RaiseMetaDataReceivedEvent(subscribedDataEventArgs); + + Assert.AreEqual(sentBytesLen, m_sentBytes.Length, "Sent bytes size not equal to published bytes size!"); Thread.Sleep(kEstimatedPublishingTime); @@ -317,6 +339,310 @@ public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryRespons subscriberConnection.Stop(); } + [Test(Description = "Validate subscriber data on first nic;" + + "Subscriber multicast ip - Publisher multicast ip;" + + "Setting Subscriber as unicast or broadcast not functional. Just discovery request to multicast and response works fine;"), Order(4)] +#if !CUSTOM_TESTS + [Ignore("A network interface controller is necessary in order to run correctly.")] +#endif + public void ValidateUadpPubSubConnectionNetworkMessageReceiveFromDiscoveryResponse_DataSetWriterConfig() + { + // Arrange + var localhost = GetFirstNic(); + Assert.IsNotNull(localhost, "localhost is null"); + Assert.IsNotNull(localhost.Address, "localhost.Address is null"); + + //discovery IP address 224.0.2.14 + IPAddress multicastIPAddress = new IPAddress(new byte[4] { 224, 0, 2, 14 }); + Assert.IsNotNull(multicastIPAddress, "multicastIPAddress is null"); + + //set configuration + string configurationFile = Utils.GetAbsoluteFilePath(m_subscriberConfigurationFileName, true, true, false); + PubSubConfigurationDataType subscriberConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(subscriberConfiguration, "subscriberConfiguration is null"); + + //set address and create subscriber + NetworkAddressUrlDataType subscriberAddress = new NetworkAddressUrlDataType(); + subscriberAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + subscriberConfiguration.Connections[0].Address = new ExtensionObject(subscriberAddress); + UaPubSubApplication subscriberApplication = UaPubSubApplication.Create(subscriberConfiguration); + Assert.IsNotNull(subscriberApplication, "subscriberApplication is null"); + + UdpPubSubConnection subscriberConnection = subscriberApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(subscriberConnection, "subscriberConnection is null"); + + //subscribe the event handlers + subscriberApplication.RawDataReceived += RawDataReceived_NoRequests; + subscriberApplication.DataSetWriterConfigurationReceived += DatasetWriterConfigurationReceived; + + //set publisher configuration an create publisher + configurationFile = Utils.GetAbsoluteFilePath(m_publisherConfigurationFileName, true, true, false); + PubSubConfigurationDataType publisherConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(publisherConfiguration, "publisherConfiguration is null"); + + NetworkAddressUrlDataType publisherAddress = new NetworkAddressUrlDataType(); + publisherAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + publisherConfiguration.Connections.First().Address = new ExtensionObject(publisherAddress); + UaPubSubApplication publisherApplication = UaPubSubApplication.Create(publisherConfiguration); + Assert.IsNotNull(publisherApplication, "publisherApplication is null"); + + UdpPubSubConnection publisherConnection = publisherApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(publisherConnection, "publisherConnection is null"); + + //start the subscriber and prepare message + subscriberConnection.Start(); + m_shutdownEvent = new ManualResetEvent(false); + m_sentBytes = PrepareDataSetWriterConfigurationMessage(publisherConnection); + + //prepare multicast client + UdpClient udpMulticastClient = new UdpClientMulticast(localhost.Address, multicastIPAddress, 0); + Assert.IsNotNull(udpMulticastClient, "udpMulticastClient is null"); + + //set endpoint and send message + IPEndPoint remoteEndPoint = new IPEndPoint(multicastIPAddress, kDiscoveryPortNo); + int sentBytesLen = udpMulticastClient.Send(m_sentBytes, m_sentBytes.Length, remoteEndPoint); + + Assert.AreEqual(sentBytesLen, m_sentBytes.Length, "Sent bytes size not equal to published bytes size!"); + + Thread.Sleep(kEstimatedPublishingTime); + + // Assert + if (!m_shutdownEvent.WaitOne(kEstimatedPublishingTime)) + { + Assert.Fail("Subscriber multicast error ... published data not received"); + } + + subscriberApplication.DataSetWriterConfigurationReceived -= DatasetWriterConfigurationReceived; + subscriberConnection.Stop(); + publisherConnection.Stop(); + } + + [Test(Description = "Validate subscriber data on first nic;" + + "Subscriber multicast ip - Publisher multicast ip;" + + "Publisher holds a DataSetWriterConfiguration, Subscriber requests the configuration;" + + "Setting Subscriber as unicast or broadcast not functional. Just discovery request to multicast and response works fine;"), Order(4)] +#if !CUSTOM_TESTS + [Ignore("A network interface controller is necessary in order to run correctly.")] +#endif + public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryResponse_SubscriberRequestDataSetWriterConfiguration() + { + // Arrange + var localhost = GetFirstNic(); + Assert.IsNotNull(localhost, "localhost is null"); + Assert.IsNotNull(localhost.Address, "localhost.Address is null"); + + //discovery IP address 224.0.2.14 + IPAddress multicastIPAddress = new IPAddress(new byte[4] { 224, 0, 2, 14 }); + Assert.IsNotNull(multicastIPAddress, "multicastIPAddress is null"); + + string configurationFile = Utils.GetAbsoluteFilePath(m_subscriberConfigurationFileName, true, true, false); + PubSubConfigurationDataType subscriberConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(subscriberConfiguration, "subscriberConfiguration is null"); + + NetworkAddressUrlDataType subscriberAddress = new NetworkAddressUrlDataType(); + subscriberAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + subscriberConfiguration.Connections[0].Address = new ExtensionObject(subscriberAddress); + UaPubSubApplication subscriberApplication = UaPubSubApplication.Create(subscriberConfiguration); + Assert.IsNotNull(subscriberApplication, "subscriberApplication is null"); + + UdpPubSubConnection subscriberConnection = subscriberApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(subscriberConnection, "subscriberConnection is null"); + + subscriberApplication.DataSetWriterConfigurationReceived += DatasetWriterConfigurationReceived; + + configurationFile = Utils.GetAbsoluteFilePath(m_publisherConfigurationFileName, true, true, false); + PubSubConfigurationDataType publisherConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(publisherConfiguration, "publisherConfiguration is null"); + + NetworkAddressUrlDataType publisherAddress = new NetworkAddressUrlDataType(); + publisherAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + publisherConfiguration.Connections.First().Address = new ExtensionObject(publisherAddress); + UaPubSubApplication publisherApplication = UaPubSubApplication.Create(publisherConfiguration); + Assert.IsNotNull(publisherApplication, "publisherApplication is null"); + + UdpPubSubConnection publisherConnection = publisherApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(publisherConnection, "publisherConnection is null"); + + m_shutdownEvent = new ManualResetEvent(false); + + publisherConnection.Start(); + // Add DataSetWriterConfiguration on Publisher + if (publisherConnection is IUadpDiscoveryMessages) + { + // set the DataSetWriterConfiguration callback waiting for a Subscriber request to grab them + ((IUadpDiscoveryMessages)publisherConnection).GetDataSetWriterConfigurationCallback(GetDataSetWriterConfiguration); + } + + //Act + subscriberConnection.Start(); + + subscriberConnection.RequestDataSetWriterConfiguration(); + + Thread.Sleep(kEstimatedPublishingTime); + + // Assert + if (!m_shutdownEvent.WaitOne(kEstimatedPublishingTime)) + { + Assert.Fail("Subscriber multicast error ... published data not received"); + } + + subscriberApplication.DataSetWriterConfigurationReceived -= DatasetWriterConfigurationReceived; + + subscriberConnection.Stop(); + publisherConnection.Stop(); + } + + [Test(Description = "Validate subscriber data on first nic;" + + "Subscriber multicast ip - Publisher multicast ip;" + + "Publisher holds a PublisherEndpoints collection, Subscriber request available PublisherEndpoints;" + + "Setting Subscriber as unicast or broadcast not functional. Just discovery request to multicast and response works fine;"), Order(4)] +#if !CUSTOM_TESTS + [Ignore("A network interface controller is necessary in order to run correctly.")] +#endif + public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryResponse_SubscriberRequestPublisherEndpoints() + { + // Arrange + var localhost = GetFirstNic(); + Assert.IsNotNull(localhost, "localhost is null"); + Assert.IsNotNull(localhost.Address, "localhost.Address is null"); + + //discovery IP address 224.0.2.14 + IPAddress multicastIPAddress = new IPAddress(new byte[4] { 224, 0, 2, 14 }); + Assert.IsNotNull(multicastIPAddress, "multicastIPAddress is null"); + + string configurationFile = Utils.GetAbsoluteFilePath(m_subscriberConfigurationFileName, true, true, false); + PubSubConfigurationDataType subscriberConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(subscriberConfiguration, "subscriberConfiguration is null"); + + NetworkAddressUrlDataType subscriberAddress = new NetworkAddressUrlDataType(); + subscriberAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + subscriberConfiguration.Connections[0].Address = new ExtensionObject(subscriberAddress); + UaPubSubApplication subscriberApplication = UaPubSubApplication.Create(subscriberConfiguration); + Assert.IsNotNull(subscriberApplication, "subscriberApplication is null"); + + UdpPubSubConnection subscriberConnection = subscriberApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(subscriberConnection, "subscriberConnection is null"); + + subscriberApplication.PublisherEndpointsReceived += PublisherEndpointsReceived; + + configurationFile = Utils.GetAbsoluteFilePath(m_publisherConfigurationFileName, true, true, false); + PubSubConfigurationDataType publisherConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(publisherConfiguration, "publisherConfiguration is null"); + + NetworkAddressUrlDataType publisherAddress = new NetworkAddressUrlDataType(); + publisherAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + publisherConfiguration.Connections.First().Address = new ExtensionObject(publisherAddress); + UaPubSubApplication publisherApplication = UaPubSubApplication.Create(publisherConfiguration); + Assert.IsNotNull(publisherApplication, "publisherApplication is null"); + + UdpPubSubConnection publisherConnection = publisherApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(publisherConnection, "publisherConnection is null"); + + m_shutdownEvent = new ManualResetEvent(false); + + publisherConnection.Start(); + // Add several PublisherEndpoints on Publisher + if (publisherConnection is IUadpDiscoveryMessages) + { + // set the publisher callback (feed with several demo PublisherEndpoints) waiting for a Subscriber request to grab them + ((IUadpDiscoveryMessages)publisherConnection).GetPublisherEndpointsCallback(GetPublisherEndpoints); + } + + //Act + subscriberConnection.Start(); + + subscriberConnection.RequestPublisherEndpoints(); + + Thread.Sleep(kEstimatedPublishingTime); + + // Assert + if (!m_shutdownEvent.WaitOne(kEstimatedPublishingTime)) + { + Assert.Fail("Subscriber multicast error ... published data not received"); + } + + subscriberApplication.PublisherEndpointsReceived -= PublisherEndpointsReceived; + + subscriberConnection.Stop(); + publisherConnection.Stop(); + } + + [Test(Description = "Validate subscriber data on first nic;" + + "Subscriber multicast ip - Publisher multicast ip;" + + "Publisher send a PublisherEndpoints collection to the Subscriber, Subscriber only listen for PublisherEndpoints;" + + "Setting Subscriber as unicast or broadcast not functional. Just discovery request to multicast and response works fine;"), Order(4)] +#if !CUSTOM_TESTS + [Ignore("A network interface controller is necessary in order to run correctly.")] +#endif + public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryResponse_PublisherTriggerEndpoints() + { + // Arrange + var localhost = GetFirstNic(); + Assert.IsNotNull(localhost, "localhost is null"); + Assert.IsNotNull(localhost.Address, "localhost.Address is null"); + + //discovery IP address 224.0.2.14 + IPAddress multicastIPAddress = new IPAddress(new byte[4] { 224, 0, 2, 14 }); + Assert.IsNotNull(multicastIPAddress, "multicastIPAddress is null"); + + string configurationFile = Utils.GetAbsoluteFilePath(m_subscriberConfigurationFileName, true, true, false); + PubSubConfigurationDataType subscriberConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(subscriberConfiguration, "subscriberConfiguration is null"); + + NetworkAddressUrlDataType subscriberAddress = new NetworkAddressUrlDataType(); + subscriberAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + subscriberConfiguration.Connections[0].Address = new ExtensionObject(subscriberAddress); + UaPubSubApplication subscriberApplication = UaPubSubApplication.Create(subscriberConfiguration); + Assert.IsNotNull(subscriberApplication, "subscriberApplication is null"); + + UdpPubSubConnection subscriberConnection = subscriberApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(subscriberConnection, "subscriberConnection is null"); + + subscriberApplication.PublisherEndpointsReceived += PublisherEndpointsReceived; + + configurationFile = Utils.GetAbsoluteFilePath(m_publisherConfigurationFileName, true, true, false); + PubSubConfigurationDataType publisherConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(publisherConfiguration, "publisherConfiguration is null"); + + NetworkAddressUrlDataType publisherAddress = new NetworkAddressUrlDataType(); + publisherAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + publisherConfiguration.Connections.First().Address = new ExtensionObject(publisherAddress); + UaPubSubApplication publisherApplication = UaPubSubApplication.Create(publisherConfiguration); + Assert.IsNotNull(publisherApplication, "publisherApplication is null"); + + UdpPubSubConnection publisherConnection = publisherApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(publisherConnection, "publisherConnection is null"); + + //Act + subscriberConnection.Start(); + + m_shutdownEvent = new ManualResetEvent(false); + + // Prepare NetworkMessage with PublisherEndpoints + m_sentBytes = PreparePublisherEndpointsMessage(publisherConnection, UdpConnectionType.Discovery); + + // Publisher: first physical network ip is mandatory on UdpClientMulticast as parameter, for multicast publisher the port must not be 4840 + UdpClient udpMulticastClient = new UdpClientMulticast(localhost.Address, multicastIPAddress, 0); + Assert.IsNotNull(udpMulticastClient, "udpMulticastClient is null"); + + IPEndPoint remoteEndPoint = new IPEndPoint(multicastIPAddress, kDiscoveryPortNo); + // Publisher: trigger PublishNetworkMessage including PublisherEndpoints data + int sentBytesLen = udpMulticastClient.Send(m_sentBytes, m_sentBytes.Length, remoteEndPoint); + Assert.AreEqual(sentBytesLen, m_sentBytes.Length, "Sent bytes size not equal to published bytes size!"); + + Thread.Sleep(kEstimatedPublishingTime); + + // Assert + if (!m_shutdownEvent.WaitOne(kEstimatedPublishingTime)) + { + Assert.Fail("Subscriber multicast error ... published data not received"); + } + + subscriberApplication.PublisherEndpointsReceived -= PublisherEndpointsReceived; + + subscriberConnection.Stop(); + } + /// /// Subscriber callback that listen for Publisher uadp notifications /// @@ -343,6 +669,7 @@ private void RawDataReceived(object sender, RawDataReceivedEventArgs e) string sentBytesStr = BitConverter.ToString(m_sentBytes); string bytesStr = BitConverter.ToString(bytes); + Assert.AreEqual(sentBytesStr, bytesStr, "Sent bytes: {0} and received bytes: {1} content are not equal", sentBytesStr, bytesStr); m_shutdownEvent.Set(); @@ -350,11 +677,113 @@ private void RawDataReceived(object sender, RawDataReceivedEventArgs e) } /// - /// Prepare data + /// Subscriber callback that listen for Publisher uadp notifications but does not test requests /// - /// + /// the sender + /// the event args + private void RawDataReceived_NoRequests(object sender, RawDataReceivedEventArgs e) + { + lock (s_lock) + { + // Assert + var localhost = GetFirstNic(); + Assert.IsNotNull(localhost, "localhost is null"); + Assert.IsNotNull(localhost.Address, "localhost.Address is null"); + + Assert.IsNotNull(e.Source, "Udp address received should not be null"); + if (localhost.Address.ToString() != e.Source.ToString()) + { + // the message comes from the network but was not initiated by test + return; + } + + byte[] bytes = e.Message; + if (bytes.Length > 12) + { + Assert.AreEqual(m_sentBytes.Length, bytes.Length, "Sent bytes size: {0} does not match received bytes size: {1}", m_sentBytes.Length, bytes.Length); + + string sentBytesStr = BitConverter.ToString(m_sentBytes); + string bytesStr = BitConverter.ToString(bytes); + + Assert.AreEqual(sentBytesStr, bytesStr, "Sent bytes: {0} and received bytes: {1} content are not equal", sentBytesStr, bytesStr); + } + m_shutdownEvent.Set(); + } + } + + /// + /// Handler for MetaDataDataReceived event. + /// + /// + /// + private void MetaDataReceived(object sender, SubscribedDataEventArgs e) + { + lock (s_lock) + { + Console.WriteLine("Metadata received:"); + bool isNetworkMessage = e.NetworkMessage is UadpNetworkMessage; + Assert.IsTrue(isNetworkMessage); + if (isNetworkMessage) + { + if (e.NetworkMessage.IsMetaDataMessage) + { + UadpNetworkMessage message = (UadpNetworkMessage)e.NetworkMessage; + + Assert.IsNotNull(message.PublisherId); + Assert.IsNotNull(message.DataSetWriterId); + Assert.IsNotNull(message.DataSetMetaData); + Assert.IsNotNull(message.DataSetMetaData.Fields); + Assert.IsTrue(message.DataSetMetaData.Fields.Count > 0); + + + Assert.IsNotNull(message.DataSetMetaData.Name); + Assert.IsNotNull(message.DataSetMetaData.ConfigurationVersion); + + for (int i = 0; i < message.DataSetMetaData.Fields.Count; i++) + { + FieldMetaData field = message.DataSetMetaData.Fields[i]; + Assert.IsNotNull(field.Name); + Assert.IsNotNull(field.DataType); + Assert.IsNotNull(field.ValueRank); + Assert.IsNotNull(field.TypeId); + Assert.IsNotNull(field.Properties); + } + } + } + m_shutdownEvent.Set(); + } + } + + /// + /// Validate received publisher endpoints + /// + /// + /// + private void PublisherEndpointsReceived(object sender, PublisherEndpointsEventArgs e) + { + lock (s_lock) + { + Assert.AreEqual(3, e.PublisherEndpoints.Length, "Send PublisherEndpoints: {0} and received PublisherEndpoints: {1} are not equal", 3, e.PublisherEndpoints.Length); + + foreach (EndpointDescription ep in e.PublisherEndpoints) + { + Assert.IsNotNull(ep.SecurityMode); + Assert.IsNotEmpty(ep.SecurityPolicyUri); + Assert.IsNotEmpty(ep.EndpointUrl); + Assert.IsNotNull(ep.Server); + } + m_shutdownEvent.Set(); + } + } + + /// + /// Prepare data / metadata for network messages + /// + /// the connection + /// the connection's type + /// the network message index /// - private byte[] PrepareData(UdpPubSubConnection publisherConnection, UdpConnectionType udpConnectionType = UdpConnectionType.Networking, int networkMessageIndex = 0) + private byte[] BuildNetworkMessages(UdpPubSubConnection publisherConnection, UdpConnectionType udpConnectionType = UdpConnectionType.Discovery, int networkMessageIndex = 0) { try { @@ -384,10 +813,179 @@ private byte[] PrepareData(UdpPubSubConnection publisherConnection, UdpConnectio return bytes; } - catch + catch (Exception ex) + { + Assert.Fail(ex.Message); + throw ex; + } + } + + /// + /// Prepare Publisher UADP Discovery request with PublisherEndpoints data + /// + /// + /// + /// + private byte[] PreparePublisherEndpointsMessage(UdpPubSubConnection publisherConnection, UdpConnectionType udpConnectionType = UdpConnectionType.Networking) + { + try + { + UaNetworkMessage networkMessage = null; + if (udpConnectionType == UdpConnectionType.Discovery) + { + List endpointDescriptions = CreatePublisherEndpoints(); + + networkMessage = publisherConnection.CreatePublisherEndpointsNetworkMessage(endpointDescriptions.ToArray(), + StatusCodes.Good, publisherConnection.PubSubConnectionConfiguration.PublisherId.Value); + Assert.IsNotNull(networkMessage, "uaNetworkMessage shall not return null"); + + return networkMessage.Encode(ServiceMessageContext.GlobalContext); + } + + return null; + } + catch (Exception ex) + { + Assert.Fail(ex.Message); + throw ex; + } + } + + /// + /// UADP Discovery: Provide Publisher demo PublisherEndpoints setting GetPublisherEndpointsCallback method to deliver them during a Subscriber request + /// + /// + private List GetPublisherEndpoints() + { + return CreatePublisherEndpoints(); + } + + /// + /// UADP Discovery: Create demo PublisherEndpoints + /// + /// + private List CreatePublisherEndpoints() + { + return new List() + { + new EndpointDescription() { + EndpointUrl = "opc.tcp://server1:4840/Test", + SecurityMode = MessageSecurityMode.None, + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#None", + Server = new ApplicationDescription() { ApplicationName = "Test security mode None", ApplicationUri = "urn:localhost:Server" } + }, + new EndpointDescription() + { + EndpointUrl = "opc.tcp://server1:4840/Test", + SecurityMode = MessageSecurityMode.Sign, + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256", + Server = new ApplicationDescription() { ApplicationName = "Test security mode Sign", ApplicationUri = "urn:localhost:Server" } + }, + new EndpointDescription() + { + EndpointUrl = "opc.tcp://server1:4840/Test", + SecurityMode = MessageSecurityMode.SignAndEncrypt, + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256", + Server = new ApplicationDescription() { ApplicationName = "Test security mode SignAndEncrypt", ApplicationUri = "urn:localhost:Server" } + } + }; + } + + /// + /// Prepare data for a DataSetWriterConfigurationMessage + /// + /// Publisher connection + /// + private byte[] PrepareDataSetWriterConfigurationMessage(UdpPubSubConnection publisherConnection) + { + try + { + WriterGroupDataType writerGroup0 = publisherConnection.PubSubConnectionConfiguration.WriterGroups.First(); + + UaNetworkMessage networkMessage = null; + + List dataSetWriterIds = new List(); + foreach (DataSetWriterDataType dataSetWriterDataType in writerGroup0.DataSetWriters) + { + dataSetWriterIds.Add(dataSetWriterDataType.DataSetWriterId); + } + networkMessage = publisherConnection.CreateDataSetWriterCofigurationMessage(dataSetWriterIds.ToArray()).First(); + + Assert.IsNotNull(networkMessage, "CreateDataSetWriterCofigurationMessages returned null"); + + byte[] bytes = networkMessage.Encode(ServiceMessageContext.GlobalContext); + + return bytes; + } + catch (Exception ex) + { + Assert.Fail(ex.Message); + throw ex; + } + } + + /// + /// Handler for DatasetWriterConfigurationReceived event. + /// + /// + /// + private void DatasetWriterConfigurationReceived(object sender, DataSetWriterConfigurationEventArgs e) + { + lock (s_lock) + { + Console.WriteLine("DataSetWriterConfig received:"); + + if (e.DataSetWriterConfiguration != null) + { + WriterGroupDataType config = e.DataSetWriterConfiguration; + + Assert.IsNotEmpty(config.Name); + Assert.IsNotNull(config.SecurityKeyServices); + Assert.IsNotNull(config.GroupProperties); + Assert.IsNotNull(config.SecurityMode); + Assert.IsNotNull(config.TransportSettings); + Assert.IsNotNull(config.MessageSettings); + Assert.IsNotEmpty(config.HeaderLayoutUri); + Assert.IsTrue(config.DataSetWriters != null); + + foreach (DataSetWriterDataType writer in config.DataSetWriters) + { + Assert.IsNotEmpty(writer.Name); + Assert.IsNotNull(writer.DataSetWriterProperties); + Assert.IsNotNull(writer.MessageSettings); + Assert.IsNotEmpty(writer.DataSetName); + } + m_shutdownEvent.Set(); + } + } + } + + /// + /// UADP Discovery: Provide DataSetWriterConfiguration setting GetDataSetWriterConfigurationCallback method to deliver them during a Subscriber request + /// + /// + private IList GetDataSetWriterConfiguration(UaPubSubApplication uaPubSubApplication) + { + return CreateDataSetWriterIdsList(uaPubSubApplication); + } + + /// + /// Create data set writer ids list from the PubSubConnectionDataType configuration + /// + /// + /// + private static IList CreateDataSetWriterIdsList(UaPubSubApplication uaPubSubApplication) + { + List ids = new List(); + + foreach (var connection in uaPubSubApplication.UaPubSubConfigurator.PubSubConfiguration.Connections) { - return Array.Empty(); + ids.AddRange(connection.WriterGroups + .Select(group => group.DataSetWriters) + .SelectMany(writer => writer.Select(x => x.DataSetWriterId)) + .ToList()); } + return ids; } } } diff --git a/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.cs b/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.cs index 350a42b3a..cd83bc280 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.cs @@ -162,7 +162,7 @@ public void ValidateUdpPubSubConnectionCreateNetworkMessage() as UadpWriterGroupMessageDataType; //Act - m_udpPublisherConnection.ResetSequenceNumber(); + UdpPubSubConnection.ResetSequenceNumber(); var networkMessages = m_udpPublisherConnection.CreateNetworkMessages(writerGroup0, new WriterGroupPublishState()); Assert.IsNotNull(networkMessages, "connection.CreateNetworkMessages shall not return null"); @@ -198,7 +198,7 @@ public void ValidateUdpPubSubConnectionCreateNetworkMessageSequenceNumber() WriterGroupDataType writerGroup0 = m_udpPublisherConnection.PubSubConnectionConfiguration.WriterGroups.First(); //Act - m_udpPublisherConnection.ResetSequenceNumber(); + UdpPubSubConnection.ResetSequenceNumber(); for (int i = 0; i < 10; i++) { // Create network message diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs index e7132e38b..f89f83a07 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs +++ b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs @@ -378,8 +378,10 @@ private void CheckPEMWriter(X509Certificate2 certificate, string password = null PEMWriter.ExportCertificateAsPEM(certificate); if (certificate.HasPrivateKey) { +#if !NETFRAMEWORK PEMWriter.ExportPrivateKeyAsPEM(certificate, password); PEMWriter.ExportECDsaPrivateKeyAsPEM(certificate); +#endif } } #endregion diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForRSA.cs b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForRSA.cs index 93f89a4ef..35f76c467 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForRSA.cs +++ b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForRSA.cs @@ -29,6 +29,7 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; @@ -84,9 +85,9 @@ protected void OneTimeTearDown() public void VerifyOneSelfSignedAppCertForAll() { var builder = CertificateBuilder.Create(Subject) - .SetNotBefore(DateTime.Today.AddYears(-1)) - .SetNotAfter(DateTime.Today.AddYears(25)) - .AddExtension(new X509SubjectAltNameExtension("urn:opcfoundation.org:mypc", new string[] { "mypc", "mypc.opcfoundation.org", "192.168.1.100" })); + .SetNotBefore(DateTime.Today.AddYears(-1)) + .SetNotAfter(DateTime.Today.AddYears(25)) + .AddExtension(new X509SubjectAltNameExtension("urn:opcfoundation.org:mypc", new string[] { "mypc", "mypc.opcfoundation.org", "192.168.1.100" })); byte[] previousSerialNumber = null; foreach (var keyHash in KeyHashPairs) { @@ -143,21 +144,30 @@ public void CreateSelfSignedForRSADefaultTest() [Theory] public void CreateSelfSignedForRSADefaultHashCustomKey( - KeyHashPair keyHashPair + KeyHashPair keyHashPair, + bool signOnly ) { // default cert with custom key - X509Certificate2 cert = CertificateBuilder.Create(Subject) - .SetRSAKeySize(keyHashPair.KeySize) - .CreateForRSA(); + var builder = CertificateBuilder.Create(Subject); + + if (signOnly) + { + // Key usage for sign only + X509KeyUsageFlags keyUsageFlags = X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation; + builder.AddExtension(new X509KeyUsageExtension(keyUsageFlags, true)); + } + + X509Certificate2 cert = builder.SetRSAKeySize(keyHashPair.KeySize).CreateForRSA(); WriteCertificate(cert, $"Default RSA {keyHashPair.KeySize} cert"); + + X509Utils.VerifyRSAKeyPair(cert, cert, true); Assert.AreEqual(Subject, cert.Subject); Assert.AreEqual(keyHashPair.KeySize, cert.GetRSAPublicKey().KeySize); Assert.AreEqual(X509Defaults.HashAlgorithmName, Oids.GetHashAlgorithmName(cert.SignatureAlgorithm.Value)); TestUtils.ValidateSelSignedBasicConstraints(cert); Assert.AreEqual(cert.SubjectName.Name, cert.IssuerName.Name); Assert.AreEqual(cert.SubjectName.RawData, cert.IssuerName.RawData); - X509Utils.VerifyRSAKeyPair(cert, cert, true); Assert.True(X509Utils.VerifySelfSigned(cert)); } @@ -349,8 +359,9 @@ public void CreateIssuerRSAWithSuppliedKeyPair() } } -#if NETFRAMEWORK || NETCOREAPP3_1 +#if NETFRAMEWORK || NETCOREAPP3_1_OR_GREATER [Test] + [SuppressMessage("Interoperability", "CA1416: Validate platform compatibility", Justification = "Test is ignored.")] public void CreateIssuerRSACngWithSuppliedKeyPair() { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -486,12 +497,12 @@ private void CheckPEMWriter(X509Certificate2 certificate, string password = null PEMWriter.ExportCertificateAsPEM(certificate); if (certificate.HasPrivateKey) { -#if NETFRAMEWORK || NETCOREAPP2_1 +#if NETFRAMEWORK || NETCOREAPP2_1 || !ECC_SUPPORT // The implementation based on bouncy castle has no support to export with password password = null; #endif PEMWriter.ExportPrivateKeyAsPEM(certificate, password); -#if NETCOREAPP3_1_OR_GREATER +#if NETCOREAPP3_1_OR_GREATER && ECC_SUPPORT PEMWriter.ExportRSAPrivateKeyAsPEM(certificate); #endif } diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj b/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj index f557ad658..7a492fa0a 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj +++ b/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,25 +7,24 @@ false - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - + + + + + + + + $(DefineConstants);ECC_SUPPORT + + + + + + - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - - - + + all @@ -35,25 +34,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - - - - - - - - - - diff --git a/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj b/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj index cf1ea9b51..44d051d09 100644 --- a/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj +++ b/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj @@ -8,9 +8,9 @@ - - - + + + all @@ -20,7 +20,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Tests/Opc.Ua.Server.Tests/ReferenceServerTest.cs b/Tests/Opc.Ua.Server.Tests/ReferenceServerTest.cs index aae66ef5f..7e4874c73 100644 --- a/Tests/Opc.Ua.Server.Tests/ReferenceServerTest.cs +++ b/Tests/Opc.Ua.Server.Tests/ReferenceServerTest.cs @@ -59,6 +59,7 @@ public class ReferenceServerTests ReferenceDescriptionCollection m_referenceDescriptions; RandomSource m_random; DataGenerator m_generator; + bool m_sessionClosed; #region Test Setup @@ -106,9 +107,12 @@ public void SetUp() [TearDown] public void TearDown() { - m_requestHeader.Timestamp = DateTime.UtcNow; - m_server.CloseSession(m_requestHeader); - m_requestHeader = null; + if (!m_sessionClosed) + { + m_requestHeader.Timestamp = DateTime.UtcNow; + m_server.CloseSession(m_requestHeader); + m_requestHeader = null; + } } #endregion @@ -142,12 +146,12 @@ public void GlobalCleanup() /// Test for expected exceptions. /// [Test] - public void ServiceResultException() + public void NoInvalidTimestampException() { - // test invalid timestamp + // test that the server accepts an invalid timestamp m_requestHeader.Timestamp = DateTime.UtcNow - TimeSpan.FromDays(30); - var sre = Assert.Throws(() => m_server.CloseSession(m_requestHeader, false)); - Assert.AreEqual(StatusCodes.BadInvalidTimestamp, sre.StatusCode); + m_server.CloseSession(m_requestHeader, false); + m_sessionClosed = true; } /// diff --git a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs index 92fb1afee..17deb7ee6 100644 --- a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs +++ b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs @@ -67,6 +67,8 @@ public async Task LoadConfiguration(string pkiRoot = null) var serverConfig = Application.Build( "urn:localhost:UA:" + typeof(T).Name, "uri:opcfoundation.org:" + typeof(T).Name) + .SetMaxByteStringLength(4 * 1024 * 1024) + .SetMaxArrayLength(1024 * 1024) .AsServer( new string[] { endpointUrl @@ -99,6 +101,12 @@ public async Task LoadConfiguration(string pkiRoot = null) MaxNodesPerWrite = 1000, MaxNodesPerMethodCall = 1000, MaxMonitoredItemsPerCall = 1000, + MaxNodesPerHistoryReadData = 1000, + MaxNodesPerHistoryReadEvents = 1000, + MaxNodesPerHistoryUpdateData = 1000, + MaxNodesPerHistoryUpdateEvents = 1000, + MaxNodesPerNodeManagement = 1000, + MaxNodesPerRegisterNodes = 1000, MaxNodesPerTranslateBrowsePathsToNodeIds = 1000 }); } diff --git a/Tests/benchmarks.cmd b/Tests/benchmarks.cmd index 6b16e53c5..ed8d7274a 100644 --- a/Tests/benchmarks.cmd +++ b/Tests/benchmarks.cmd @@ -9,7 +9,7 @@ rem // remove trailing slash set current-path=%current-path:~0,-1% set build_root=%current-path%\.. set framework=net48 -set runtimes=net48 netcoreapp3.1 net6.0 +set runtimes=net48 net6.0 net8.0 set filter=* cd %build_root% diff --git a/Tests/customtest.bat b/Tests/customtest.bat new file mode 100644 index 000000000..6cb0d6c79 --- /dev/null +++ b/Tests/customtest.bat @@ -0,0 +1,49 @@ +@echo off +setlocal enabledelayedexpansion + +echo This script is used to run custom platform tests for the UA Core Library +echo Supported parameters: net462, netstandard2.0, netstandard2.1, net48, net6.0, net8.0 + +REM Check if the target framework parameter is provided +if "%1"=="" ( + echo Usage: %0 [TargetFramework] + echo Allowed values for TargetFramework: net462, netstandard2.0, netstandard2.1, net48, net6.0, net8.0, default + goto :eof +) + +REM Check if the provided TargetFramework is valid +set "validFrameworks= default net462 netstandard2.0 netstandard2.1 net48 net6.0 net8.0" +if "!validFrameworks: %1 =!"=="%validFrameworks%" ( + echo Invalid TargetFramework specified. Allowed values are: default, net462, netstandard2.0, netstandard2.1, net48, net6.0, net8.0 + goto :eof +) + +if "%1"=="default" ( + echo Using the default targets for the test runners. + goto :cleanup +) + +REM this is the variable used to switch the build scripts to a dedicated target +echo Using the %1 CustomTestTargets for the test runners. +set CustomTestTarget=%1 + +:cleanup + +REM clean up all obj and bin folders + +REM Set the root directory path as batch file location +set "root=%~dp0" + +REM Delete 'obj' and 'bin' folders +for /d /r "%root%" %%d in (*obj *bin) do ( + echo Deleting "%%d" + del /S /F /Q "%%d\*.*" + rmdir /s /q "%%d" +) + +echo Clean up complete. + +echo restore %1 +dotnet restore -f "..\UA Core Library.sln" +dotnet build --no-restore "..\UA Core Library.sln" +dotnet test "..\UA Core Library.sln" diff --git a/UA Core Library.sln b/UA Core Library.sln index 1a5713a1e..4f68a3306 100644 --- a/UA Core Library.sln +++ b/UA Core Library.sln @@ -47,6 +47,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "azurepipelines", "azurepipelines", "{1C25BE72-C337-42AE-9F7C-D6B45F7B7079}" ProjectSection(SolutionItems) = preProject .azurepipelines\ci.yml = .azurepipelines\ci.yml + .azurepipelines\customtest.yml = .azurepipelines\customtest.yml .azurepipelines\get-matrix.ps1 = .azurepipelines\get-matrix.ps1 .azurepipelines\get-root.ps1 = .azurepipelines\get-root.ps1 .azurepipelines\get-version.ps1 = .azurepipelines\get-version.ps1 @@ -83,6 +84,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Stack", "Stack", "{12710D16-90C2-44B1-8B62-8046C1023055}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "actions", "actions", "{82AA8918-FD9F-4477-A131-874673BDC3B9}" + ProjectSection(SolutionItems) = preProject + .github\workflows\buildandtest.yml = .github\workflows\buildandtest.yml + .github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml + .github\workflows\docker-image.yml = .github\workflows\docker-image.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -191,6 +199,7 @@ Global {D9985EA3-0A0F-4552-8BFA-65E282EFC6EA} = {DDDC86D8-79F7-488B-B445-AD176FAE1603} {FC588507-282A-426E-8BC9-940860C76610} = {DDDC86D8-79F7-488B-B445-AD176FAE1603} {9FC5C47D-5070-4222-B4F4-94EB495AEE4C} = {EE37EB25-F581-4794-9CAC-F15BF13D520C} + {82AA8918-FD9F-4477-A131-874673BDC3B9} = {0152A569-8287-4E49-9F02-E27FD700B719} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {EA3AA3AE-CE8A-4DF1-BED8-CA0BAFC87303} diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 66c077e40..7ee3c240a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -25,7 +25,7 @@ pr: variables: FullBuild: ${{ or(ne(variables['Build.Reason'], 'PullRequest'), ne(variables['System.PullRequest.IsFork'], 'False')) }} - ScheduledBuild: ${{ eq(variables['Build.Reason'], 'Schedule') }} + ScheduledBuild: ${{ in(variables['Build.Reason'], 'Schedule', 'Manual') }} stages: - stage: build @@ -64,16 +64,6 @@ stages: framework: net48 agents: '@{ windows = "windows-2019" }' jobnamesuffix: net48 - - template: .azurepipelines/test.yml - parameters: - configuration: Release - framework: netcoreapp3.1 - jobnamesuffix: core3 -- stage: test6release - dependsOn: [build] - displayName: 'Test .NET 6 Core and SDK Release' - condition: and(succeeded(), ne(variables.ScheduledBuild, 'False')) - jobs: - template: .azurepipelines/test.yml parameters: configuration: Release @@ -82,7 +72,7 @@ stages: jobnamesuffix: net60 - stage: testreleasepr dependsOn: [] - displayName: 'Fast PR Test Core and SDK Release' + displayName: 'Fast .NET 6.0 PR Test' condition: and(eq(variables.FullBuild, 'False'), eq(variables.ScheduledBuild, 'False')) jobs: - template: .azurepipelines/test.yml @@ -93,13 +83,60 @@ stages: jobnamesuffix: net60pr - stage: testdebug dependsOn: [build] - displayName: 'Test Core and SDK Debug' + displayName: 'Test .NET 6.0 Debug' condition: and(succeeded(), ne(variables.ScheduledBuild, 'False')) jobs: - template: .azurepipelines/test.yml parameters: - framework: netcoreapp3.1 + framework: net6.0 configuration: Debug + jobnamesuffix: net60debug +- stage: testnet80 + dependsOn: [build] + displayName: 'Test .NET 8.0' + condition: and(succeeded(), ne(variables.ScheduledBuild, 'False')) + jobs: + - template: .azurepipelines/test.yml + parameters: + framework: net8.0 + configuration: Release + jobnamesuffix: net80 + customtestarget: net8.0 +- stage: testnet462 + dependsOn: [build] + displayName: 'Test .NET 4.6.2' + condition: and(succeeded(), ne(variables.ScheduledBuild, 'False')) + jobs: + - template: .azurepipelines/test.yml + parameters: + framework: net462 + configuration: Release + jobnamesuffix: net462 + agents: '@{ windows = "windows-2022" }' + customtestarget: net462 +- stage: testnetstandard20 + dependsOn: [build] + displayName: 'Test .NETStandard 2.0' + condition: and(succeeded(), ne(variables.ScheduledBuild, 'False')) + jobs: + - template: .azurepipelines/test.yml + parameters: + framework: net6.0 + configuration: Release + jobnamesuffix: netstandard20 + agents: '@{ windows = "windows-2019"; linux="ubuntu-20.04"}' + customtestarget: netstandard2.0 +- stage: testnetstandard21 + dependsOn: [build] + displayName: 'Test .NETStandard 2.1' + condition: and(succeeded(), ne(variables.ScheduledBuild, 'False')) + jobs: + - template: .azurepipelines/test.yml + parameters: + framework: net6.0 + configuration: Release + jobnamesuffix: netstandard21 + customtestarget: netstandard2.1 - stage: coverage dependsOn: [testdebug,testrelease] displayName: 'Code Coverage' @@ -109,5 +146,6 @@ stages: parameters: configuration: Release poolImage: 'ubuntu-22.04' - framework: net6.0 + framework: net8.0 jobnamesuffix: net60cc + customtestarget: net8.0 diff --git a/common.props b/common.props index c8b0c5425..bbfba2af1 100644 --- a/common.props +++ b/common.props @@ -2,7 +2,7 @@ OPC UA .NET Standard Library https://github.com/OPCFoundation/UA-.NETStandard - 1.04.368 + 1.04.372 preview-$([System.DateTime]::Now.ToString("yyyyMMdd")) Copyright © 2004-2023 OPC Foundation, Inc OPC Foundation @@ -48,7 +48,7 @@ - + diff --git a/targets.props b/targets.props index 7ab73c1d7..4f38acb57 100644 --- a/targets.props +++ b/targets.props @@ -1,23 +1,107 @@ + + + + - + + + + true + preview-all + net462 + net462 + net462 + net462 + net462 + net462 + + + + + true + preview-all + net6.0 + net6.0 + net6.0 + netstandard2.0 + netstandard2.1 + netstandard2.0 + + + + + preview-all + net6.0 + net6.0 + net6.0 + netstandard2.1 + netstandard2.1 + netstandard2.1 + + + + + preview-all + net48 + net48 + net48 + net48 + net48 + net48 + + + + + preview-all + net6.0 + net6.0 + net6.0 + net6.0 + net6.0 + net6.0 + + + + + preview-all + net8.0 + net8.0 + net8.0 + net8.0 + net8.0 + net8.0 + + + preview-all - net6.0;netcoreapp3.1;net48 + net6.0;net48 net6.0 - net48;netcoreapp3.1;net6.0 - net48;netstandard2.0;netstandard2.1;net6.0 - net48;netstandard2.1;net6.0 - net48;netstandard2.0;netcoreapp3.1;net6.0 + net48;net6.0 + net48;netstandard2.0;netstandard2.1;net6.0;net8.0 + net48;netstandard2.1;net6.0;net8.0 + net48;netstandard2.0;netcoreapp3.1;net6.0;net8.0