Files
UA-.NETStandard/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs
Marc Schier 2966f693b2 2.0 Migration Analyzer and codefixer with Agent update (#3820)
Introduces `OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer` — a
single NuGet package that ships **three Roslyn components + a runtime
shim** to make the 1.5.378 → 2.0 upgrade as close to "install package,
run `dotnet format`, remove package" as possible:

| DLL | Where | Purpose |
|-----|-------|---------|
| `Opc.Ua.MigrationAnalyzer.dll` | `analyzers/dotnet/cs/` | 19 Roslyn
analyzers (`UA0001`–`UA0022`) covering every breaking-change pattern in
[`Docs/MigrationGuide.md`](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Docs/MigrationGuide.md).
Built against the stable Roslyn 4.14 analyzer API — loads cleanly in
csc.exe (no `Microsoft.CodeAnalysis.Workspaces` reference). |
| `Opc.Ua.MigrationAnalyzer.CodeFixer.dll` | `analyzers/dotnet/cs/` | 14
companion `CodeFixProvider`s; loaded only by Workspaces-aware hosts
(Visual Studio, `dotnet format analyzers`). |
| `Opc.Ua.MigrationAnalyzer.Generator.dll` | `analyzers/dotnet/cs/` |
Roslyn `IIncrementalGenerator` that emits per-consumer `internal sealed
[Obsolete] class <Name>Collection : List<TElement>` shims for every
`<Type>Collection` wrapper the consumer references — including
**arbitrary model-compiled `<UserType>Collection` patterns**, not just
the 30 well-known built-in ones. |
| `Opc.Ua.MigrationAnalyzer.Core.dll` | `lib/<tfm>/` for `net472, net48,
netstandard2.1, net8.0, net9.0, net10.0` | 1.5.378 → 2.0 runtime
compatibility shim. Re-exposes the obsolete extension surface so
1.5.378-style call sites keep compiling against 2.0 with warnings
instead of errors. |

### Migration workflow the package enables

1. **Bump every `OPCFoundation.NetStandard.Opc.Ua.*` PackageReference to
`2.0.*-*`.**
2. **Add the migration package** as a build-only dependency:
   ```xml
<PackageReference
Include="OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer"
                     Version="2.0.*-*"
                     PrivateAssets="all" />
   ```
3. **Run `dotnet build`.** The Core shim DLL re-supplies obsolete
extension members; the source generator covers `<Type>Collection`
references; what remains are `[Obsolete]` warnings + `UA00xx` analyzer
diagnostics + (occasionally) `MIG01` for unresolvable model-compiled
element types.
4. **Apply auto-fixes** in the IDE or from the command line:
   ```bash
   dotnet format analyzers <solution>.sln \
       --diagnostics UA0002 UA0003 UA0004 UA0005 UA0006 UA0007 UA0008 \
                     UA0009 UA0010 UA0012 UA0014 UA0019 UA0020 UA0022 \
       --severity warn
   ```
5. **Walk the residual** `UA0001` / `UA0011` / `UA0015` / `UA0018` /
`UA0021` diagnostics manually — they cover patterns that require human
judgement.
6. **Remove the migration package.** You are now on clean 2.0 with no
shim dependency.

### Source generator (`Opc.Ua.MigrationAnalyzer.Generator.dll`)

Per-consumer Roslyn `IIncrementalGenerator` that emits `.g.cs` files for
every `<Name>Collection` reference in the consumer's compilation that
fails to bind:

- **Catalog override (30 entries)** — pins element types that renamed
across the 1.5.378 → 2.0 boundary: `DateTime`→`DateTimeUtc`,
`Guid`→`Uuid`, `byte[]`→`ByteString`, `XmlElement`→`Opc.Ua.XmlElement`,
plus the primitives and built-in opc types whose element name didn't
rename.
- **Semantic lookup** — for model-compiled `<UserType>Collection`
patterns the model compiler historically emitted for any user-defined
complex type. Strips the `Collection` suffix, runs
`Compilation.GetSymbolsWithName` against the unique element short name,
emits the shim with the fully-qualified type reference.
- **`MIG01` diagnostic** — fires when the element type can't be uniquely
resolved (zero or multiple candidates). User adds the missing `using` or
migrates the site manually.

Each generated file emits a class shaped exactly like the 1.5.378
wrapper:

```csharp
[global::System.Obsolete("'Int32Collection' was removed in 2.0. Use 'List<int>' or 'ArrayOf<int>' instead. (UA0002)")]
internal sealed class Int32Collection : global::System.Collections.Generic.List<int>
{
    public Int32Collection() { }
    public Int32Collection(int capacity) : base(capacity) { }
    public Int32Collection(global::System.Collections.Generic.IEnumerable<int> collection) : base(collection) { }
    public static implicit operator global::Opc.Ua.ArrayOf<int>(Int32Collection? value)
        => value is null ? default : value.ToArrayOf();
}
```

**Design choice — `internal` (not `public`):** the generated shim never
leaks through the consumer's public API surface. Consumers with *public*
methods or properties returning a `<Type>Collection` will hit `CS0050:
Inconsistent accessibility` — the intended signal that the public
surface must migrate to `List<T>` / `ArrayOf<T>` first; internal call
sites keep compiling under the shim so iteration is incremental.

### 19-rule analyzer reference

| Rule | Replaces | Default | Auto-fix |
|------|----------|---------|----------|
| `UA0001` | `Utils.Trace` / `Utils.LogX` | Info | No (telemetry
plumbing requires manual review) |
| `UA0002` | Removed `<Type>Collection` wrappers | Warning | Yes (→
`List<T>` / `ArrayOf<T>`); the source generator keeps the call site
compiling in the meantime |
| `UA0003` | `x == null` on now-struct built-ins | Warning | Yes (→
`x.IsNull`) |
| `UA0004` | `?.` on now-struct built-ins | Warning | Yes (drop the `?`)
|
| `UA0005` | `byte[]` where `ByteString` is expected | Warning | Yes
(append `.ToByteString()`) |
| `UA0006` | `new Variant(object\|DateTime\|Guid\|byte[])` | Warning |
Yes (→ `Variant.From(...)`) |
| `UA0007` | `new NodeId(string)` / `new ExpandedNodeId(string)` |
Warning | Yes (→ `Parse`) |
| `UA0008` | `Session.Call(..., params object[])` | Warning | Yes (wrap
with `Variant.From`) |
| `UA0009` | `[DataContract]` / `[DataMember]` on config extensions |
Warning | Yes (→ `[DataType]` / `[DataTypeField]`) |
| `UA0010` | `using` / `Dispose` on `CertificateIdentifier` /
`UserIdentity` / `IUserIdentityTokenHandler` | Warning | Yes (drop the
disposable) |
| `UA0011` | Sync
`IUserIdentityTokenHandler.{Encrypt,Decrypt,Sign,Verify}` | Info | No
(async promotion required) |
| `UA0012` | `CertificateFactory.*` static helpers | Warning | Yes (→
instance methods) |
| `UA0014` | `DataValue.IsGood(dv)` static helper | Warning | Yes (→
`dv.IsGood`) |
| `UA0015` | Sync / APM members on GDS / LDS clients | Info | No (async
promotion required) |
| `UA0018` | `CertificateIdentifier.Certificate` getter | Info | No (→
`LoadCertificate2Async`) |
| `UA0019` | `new DataValue(StatusCode[, ts])` | Warning | Yes (→
object-initializer form) |
| `UA0020` | `EncodeableFactory.GlobalFactory` / `Create()` | Warning |
Yes (→ `ServiceMessageContext.Factory` / `Fork()`) |
| `UA0021` | `CertificateValidator` / `CertificateValidationEventArgs`
(structural rename) | Info | No (see migration guide §Certificate
validation) |
| `UA0022` | `ApplicationConfiguration.CertificateValidator` /
`ServerBase.CertificateValidator` property rename | Warning | Yes (→
`.CertificateManager`) |

Plus one generator-only diagnostic:

| Rule | Source | Default | Triggers |
|------|--------|---------|----------|
| `MIG01` | Generator | Warning | Generator can't uniquely resolve the
element type for a `<Foo>Collection` reference (zero or multiple
candidates). The user adds a `using` for the namespace defining `Foo`,
or migrates the call site manually. |

### Runtime shim coverage (`Opc.Ua.MigrationAnalyzer.Core.dll`)

C# 14 `extension` members so 1.5.378 call sites continue to compile:

- **Moved obsolete extensions** the 2.0 libraries no longer carry
inline: `NodeId` / `Variant` / `DataValue` null-check helpers, `Session`
sync helpers, `Subscription` sync helpers, `ApplicationInstance`
helpers, `ServerBase.Start` / `Stop`, `TransportChannel` APM,
`ChannelBase` static factory methods, etc.
- **New shims for genuinely-removed members**:
`EncodeableFactory.GlobalFactory`, `CertificateIdentifier.Certificate`
(throws), sync wrappers for
`IUserIdentityTokenHandler.{Encrypt,Decrypt,Sign,Verify}`, sync + APM
wrappers for the GDS / LDS client APIs, `GlobalDiscoverySampleServer`
1.5.378-shape ctor (in-tree `Libraries/Opc.Ua.Gds.Server.Common/`).

> ⚠ The sync shims wrap their `*Async` counterparts via
`Task.Run(…).GetAwaiter().GetResult()`. Intended as a **migration aid
only** — port to `await` before shipping.

### Repo layout

```
Tools/Opc.Ua.MigrationAnalyzer/                  # analyzer DLL
  Analyzers/UA00xxAnalyzer.cs                    # 19 DiagnosticAnalyzer impls
  Diagnostics/{DiagnosticIds,Descriptors,WellKnownProperties}.cs
  Helpers/{SymbolExtensions,UaSymbols}.cs
  Opc.Ua.MigrationAnalyzer.nuspec                # ships all 3 analyzer DLLs + 6-TFM Core
  OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer.props

Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/        # code-fix DLL
  CodeFixes/UA00xxCodeFix.cs                     # 14 CodeFixProvider impls

Tools/Opc.Ua.MigrationAnalyzer.Generator/        # source generator DLL  (NEW)
  MigrationGenerator.cs                          # IIncrementalGenerator
  CollectionShimCatalog.cs                       # 30-entry element-type overrides
  Diagnostics/GeneratorDiagnostics.cs            # MIG01

Tools/Opc.Ua.MigrationAnalyzer.Core/             # runtime shim (multi-TFM)
  Client/, Configuration/, Core/, Gds.Client.Common/, Types/, Marker/

Tools/Roslyn.slnx                                # source-gen + migration projects

Tests/Opc.Ua.MigrationAnalyzer.Tests/            # 142 tests
  Analyzers/UA00xxTests.cs                       # 107 analyzer + code-fix tests
  Generators/MigrationGeneratorTests.cs          # 35 generator tests (NEW)
Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/       # 13 runtime-shim tests
```

### Documentation

-
[`Docs/MigrationGuide.md`](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Docs/MigrationGuide.md)
— §3 Collection Type Migration gains a tip pointing at the generator so
CS0246 is replaced with `[Obsolete]` warnings + UA0002.
-
[`.github/agents/opcua-v20-migration.agent.md`](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/.github/agents/opcua-v20-migration.agent.md)
— Strategy section mentions the three Roslyn components + runtime shim
explicitly, plus the MIG01 diagnostic and the "internal-by-design"
CS0050 hint.

### Central package pinning

`Directory.Packages.props` pins `Microsoft.CodeAnalysis.*` (Common,
CSharp, CSharp.Workspaces, Workspaces.Common) to **4.14.0** — the stable
analyzer API surface. The earlier 5.3 pin was the *csc-internal*
version, which is not safe for analyzer hosts.

### Dogfood story

The full 42-sample dogfood against
`OPCFoundation/UA-.NETStandard-Samples` surfaced a **critical
analyzer-loader bug** in an earlier iteration: the analyzer DLL
initially co-shipped its code-fix providers in one assembly, which
transitively referenced `Microsoft.CodeAnalysis.Workspaces.dll`.
csc.exe's analyzer host ships only `Microsoft.CodeAnalysis.dll` +
`CSharp.dll` in its bincore and **silently swallows** load failures —
net result: zero diagnostics across all samples. Fix landed in commit
`861fa6ee1`:

1. **Split** the assembly: analyzer in `Opc.Ua.MigrationAnalyzer.dll`
(Workspaces-free, csc-safe), code-fixes in
`Opc.Ua.MigrationAnalyzer.CodeFixer.dll` (Workspaces-aware, IDE-only).
2. **Downgrade** to Roslyn 4.14 — the stable analyzer API.
3. `/p:ReportAnalyzer=true` confirms all 19 analyzers initialise and
execute on real consumer code.

This PR's new generator was smoke-tested against a minimal consumer that
exercises all 30 catalog entries + a model-compiled element type + the
unresolvable case:
- 30 catalog patterns + model-compiled `MyThingCollection` → all emit
shims, fire CS0618 with `(UA0002)` marker
- Element-type renames verified: `DateTimeCollection` → `DateTimeUtc`,
`ByteStringCollection` → `ByteString`, `GuidCollection` → `Uuid`, etc.
- `NeverHeardOfItCollection` → `MIG01` fires with help link to the
migration guide

Known compatibility gaps (documented in the migration agent):
- Legacy WinForms `.Net4` sample projects use pre-SDK MSBuild XML and
**ignore** `Directory.Build.targets` `<PackageReference>` injection —
consumers in this format must add the migration package to their csproj
inline.

### Packaging design notes

- Hand-authored nuspec ships 3 DLLs in `analyzers/dotnet/cs/` + 6 TFMs
of Core (shim) DLLs under `lib/`. All 6 OPC UA runtime package
dependencies are declared per-TFM-group.
- `build/OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer.props` is
auto-imported by NuGet (file name must match package id exactly) and
injects all three analyzer DLLs as `<Analyzer>` items.
- `DiagnosticIds.cs` / `DiagnosticDescriptors.cs` /
`WellKnownProperties.cs` / `Helpers/*.cs` are **shared via `<Compile
Link=…>`** between the analyzer and code-fixer projects to break what
would otherwise be a NuGet restore cycle. The generator has no compile
dependency on the analyzer; the 30-entry override table is duplicated in
`CollectionShimCatalog.cs` (the analyzer table also drives diagnostic
messages, so the duplication is small and intentional).
- `Opc.Ua.MigrationAnalyzer.Core` is multi-TFM (`net472, net48,
netstandard2.1, net8.0, net9.0, net10.0`) and **non-packable on its
own**; the parent migration package's nuspec consumes each TFM's DLL by
path.
2026-06-05 12:01:53 +02:00

1494 lines
58 KiB
C#

/* ========================================================================
* Copyright (c) 2005-2025 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.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
namespace Opc.Ua.Configuration
{
/// <summary>
/// A class that builds a configuration for a UA application.
/// </summary>
/// <remarks>
/// This builder uses a fluent staged-interface pattern: callers progress through
/// <see cref="ApplicationInstance.Build(string, string)"/> (which initializes
/// <see cref="ApplicationConfiguration.TransportQuotas"/> and
/// <see cref="ApplicationConfiguration.TraceConfiguration"/>),
/// then through <see cref="AsClient"/>/<see cref="AsServer(string[], string[])"/>
/// (which initialize <see cref="ApplicationConfiguration.ClientConfiguration"/>
/// and <see cref="ApplicationConfiguration.ServerConfiguration"/> respectively),
/// before reaching the per-section setter methods. As a result the
/// <c>!</c> operators on accesses to those nullable sub-configuration objects in
/// this class are guaranteed to be non-null at the time the methods are invoked.
/// </remarks>
public class ApplicationConfigurationBuilder : IApplicationConfigurationBuilder
{
/// <summary>
/// Create the application instance builder.
/// </summary>
public ApplicationConfigurationBuilder(ApplicationInstance applicationInstance)
{
ApplicationInstance = applicationInstance;
}
/// <summary>
/// The application instance used to build the configuration.
/// </summary>
public IApplicationInstance ApplicationInstance { get; }
/// <summary>
/// The application configuration.
/// </summary>
/// <remarks>
/// This builder is constructed by <see cref="ApplicationInstance.Build"/>, which assigns
/// <see cref="IApplicationInstance.ApplicationConfiguration"/> before returning the builder,
/// so the underlying configuration is guaranteed to be non-null.
/// </remarks>
public ApplicationConfiguration ApplicationConfiguration
=> ApplicationInstance.ApplicationConfiguration!;
/// <inheritdoc/>
public IApplicationConfigurationBuilderGlobalConfiguration SetHiResClockDisabled(
bool hiResClockDisabled)
{
ApplicationConfiguration.DisableHiResClock = hiResClockDisabled;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderClientSelected AsClient()
{
switch (ApplicationInstance.ApplicationType)
{
case ApplicationType.Client:
case ApplicationType.ClientAndServer:
break;
case ApplicationType.Server:
ApplicationInstance.ApplicationType = m_typeSelected
? ApplicationType.ClientAndServer
: ApplicationType.Client;
break;
case ApplicationType.DiscoveryServer:
throw new ArgumentException("Discovery server type is not allowed as client.");
default:
throw new ArgumentException("Invalid application type for client.");
}
m_typeSelected = true;
ApplicationConfiguration.ClientConfiguration = new ClientConfiguration();
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderSecurityOptions AddSecurityConfiguration(
string subjectName,
string? pkiRoot = null,
string? appRoot = null,
string? rejectedRoot = null)
{
pkiRoot = DefaultPKIRoot(pkiRoot);
appRoot = appRoot == null ? pkiRoot : DefaultPKIRoot(appRoot);
rejectedRoot = rejectedRoot == null ? pkiRoot : DefaultPKIRoot(rejectedRoot);
string appStoreType = CertificateStoreIdentifier.DetermineStoreType(appRoot);
string pkiRootType = CertificateStoreIdentifier.DetermineStoreType(pkiRoot);
string rejectedRootType = CertificateStoreIdentifier.DetermineStoreType(rejectedRoot);
ApplicationConfiguration.SecurityConfiguration = new SecurityConfiguration
{
// app cert store
ApplicationCertificate = new CertificateIdentifier
{
StoreType = appStoreType,
StorePath = DefaultCertificateStorePath(TrustlistType.Application, appRoot),
SubjectName = Utils.ReplaceDCLocalhost(subjectName)
},
// App trusted & issuer
TrustedPeerCertificates = new CertificateTrustList
{
StoreType = pkiRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.Trusted, pkiRoot)
},
TrustedIssuerCertificates = new CertificateTrustList
{
StoreType = pkiRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.Issuer, pkiRoot)
},
// Https trusted & issuer
TrustedHttpsCertificates = new CertificateTrustList
{
StoreType = pkiRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.TrustedHttps, pkiRoot)
},
HttpsIssuerCertificates = new CertificateTrustList
{
StoreType = pkiRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.IssuerHttps, pkiRoot)
},
// User trusted & issuer
TrustedUserCertificates = new CertificateTrustList
{
StoreType = pkiRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.TrustedUser, pkiRoot)
},
UserIssuerCertificates = new CertificateTrustList
{
StoreType = pkiRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.IssuerUser, pkiRoot)
},
// rejected store
RejectedCertificateStore = new CertificateStoreIdentifier
{
StoreType = rejectedRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.Rejected, rejectedRoot)
}
};
SetSecureDefaults(ApplicationConfiguration.SecurityConfiguration);
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderSecurityOptions AddSecurityConfiguration(
ArrayOf<CertificateIdentifier> certIdList,
string? pkiRoot = null,
string? rejectedRoot = null)
{
pkiRoot = DefaultPKIRoot(pkiRoot);
rejectedRoot = rejectedRoot == null ? pkiRoot : DefaultPKIRoot(rejectedRoot);
string pkiRootType = CertificateStoreIdentifier.DetermineStoreType(pkiRoot);
string rejectedRootType = CertificateStoreIdentifier.DetermineStoreType(rejectedRoot);
ApplicationConfiguration.SecurityConfiguration = new SecurityConfiguration
{
// app cert store
ApplicationCertificates = certIdList,
// App trusted & issuer
TrustedPeerCertificates = new CertificateTrustList
{
StoreType = pkiRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.Trusted, pkiRoot)
},
TrustedIssuerCertificates = new CertificateTrustList
{
StoreType = pkiRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.Issuer, pkiRoot)
},
// Https trusted & issuer
TrustedHttpsCertificates = new CertificateTrustList
{
StoreType = pkiRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.TrustedHttps, pkiRoot)
},
HttpsIssuerCertificates = new CertificateTrustList
{
StoreType = pkiRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.IssuerHttps, pkiRoot)
},
// User trusted & issuer
TrustedUserCertificates = new CertificateTrustList
{
StoreType = pkiRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.TrustedUser, pkiRoot)
},
UserIssuerCertificates = new CertificateTrustList
{
StoreType = pkiRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.IssuerUser, pkiRoot)
},
// rejected store
RejectedCertificateStore = new CertificateStoreIdentifier
{
StoreType = rejectedRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.Rejected, rejectedRoot)
}
};
SetSecureDefaults(ApplicationConfiguration.SecurityConfiguration);
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderSecurityOptionStores AddSecurityConfigurationStores(
string subjectName,
string appRoot,
string trustedRoot,
string issuerRoot,
string? rejectedRoot = null)
{
string appStoreType = CertificateStoreIdentifier.DetermineStoreType(appRoot);
string issuerRootType = CertificateStoreIdentifier.DetermineStoreType(issuerRoot);
string trustedRootType = CertificateStoreIdentifier.DetermineStoreType(trustedRoot);
rejectedRoot ??= DefaultPKIRoot(null);
string rejectedRootType = CertificateStoreIdentifier.DetermineStoreType(rejectedRoot);
ApplicationConfiguration.SecurityConfiguration = new SecurityConfiguration
{
// app cert store
ApplicationCertificate = new CertificateIdentifier
{
StoreType = appStoreType,
StorePath = DefaultCertificateStorePath(TrustlistType.Application, appRoot),
SubjectName = Utils.ReplaceDCLocalhost(subjectName)
},
// App trusted & issuer
TrustedPeerCertificates = new CertificateTrustList
{
StoreType = trustedRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.Trusted, trustedRoot)
},
TrustedIssuerCertificates = new CertificateTrustList
{
StoreType = issuerRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.Issuer, issuerRoot)
},
// rejected store
RejectedCertificateStore = new CertificateStoreIdentifier
{
StoreType = rejectedRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.Rejected, rejectedRoot)
}
};
SetSecureDefaults(ApplicationConfiguration.SecurityConfiguration);
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderSecurityOptionStores AddSecurityConfigurationUserStore(
string trustedRoot,
string issuerRoot)
{
string trustedRootType = CertificateStoreIdentifier.DetermineStoreType(trustedRoot);
string issuerRootType = CertificateStoreIdentifier.DetermineStoreType(issuerRoot);
// User trusted & issuer
ApplicationConfiguration.SecurityConfiguration.TrustedUserCertificates
= new CertificateTrustList
{
StoreType = trustedRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.TrustedUser, trustedRoot)
};
ApplicationConfiguration.SecurityConfiguration.UserIssuerCertificates
= new CertificateTrustList
{
StoreType = issuerRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.IssuerUser, issuerRoot)
};
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderSecurityOptionStores AddSecurityConfigurationHttpsStore(
string trustedRoot,
string issuerRoot)
{
string trustedRootType = CertificateStoreIdentifier.DetermineStoreType(trustedRoot);
string issuerRootType = CertificateStoreIdentifier.DetermineStoreType(issuerRoot);
// Https trusted & issuer
ApplicationConfiguration.SecurityConfiguration.TrustedHttpsCertificates
= new CertificateTrustList
{
StoreType = trustedRootType,
StorePath = DefaultCertificateStorePath(
TrustlistType.TrustedHttps,
trustedRootType)
};
ApplicationConfiguration.SecurityConfiguration.HttpsIssuerCertificates
= new CertificateTrustList
{
StoreType = issuerRootType,
StorePath = DefaultCertificateStorePath(TrustlistType.IssuerHttps, issuerRoot)
};
return this;
}
/// <inheritdoc/>
public async Task<ApplicationConfiguration> CreateAsync(CancellationToken ct = default)
{
// sanity checks
if (ApplicationInstance.ApplicationType is
ApplicationType.Server or ApplicationType.ClientAndServer &&
ApplicationConfiguration.ServerConfiguration == null)
{
throw new ArgumentException("ApplicationType Server is not configured.");
}
if (ApplicationInstance.ApplicationType is
ApplicationType.Client or ApplicationType.ClientAndServer &&
ApplicationConfiguration.ClientConfiguration == null)
{
throw new ArgumentException("ApplicationType Client is not configured.");
}
// ensure for a user token policy
if (ApplicationConfiguration.ServerConfiguration?.UserTokenPolicies.Count == 0)
{
ApplicationConfiguration.ServerConfiguration!.UserTokenPolicies +=
new UserTokenPolicy(UserTokenType.Anonymous);
}
// ensure for secure transport profiles
if (ApplicationConfiguration.ServerConfiguration?.SecurityPolicies.Count == 0)
{
AddSecurityPolicies();
}
// Legacy Utils trace pipeline; kept for 1.5.378 -> 2.0 migration.
#pragma warning disable CS0618 // Type or member is obsolete
ApplicationConfiguration.TraceConfiguration?.ApplySettings();
#pragma warning restore CS0618 // Type or member is obsolete
await ApplicationConfiguration.ValidateAsync(ApplicationInstance.ApplicationType, ct)
.ConfigureAwait(false);
return ApplicationConfiguration;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerSelected AsServer(
string[] baseAddresses,
string[]? alternateBaseAddresses = null)
{
switch (ApplicationInstance.ApplicationType)
{
case ApplicationType.Client:
ApplicationInstance.ApplicationType = m_typeSelected
? ApplicationType.ClientAndServer
: ApplicationType.Server;
break;
case ApplicationType.Server:
case ApplicationType.ClientAndServer:
break;
case ApplicationType.DiscoveryServer:
throw new ArgumentException("Discovery server type is not allowed as server.");
default:
throw new ArgumentException("Invalid application type for server.");
}
m_typeSelected = true;
// configure a server
var serverConfiguration = new ServerConfiguration
{
// by default disable LDS registration
MaxRegistrationInterval = 0
};
// base addresses
foreach (string baseAddress in baseAddresses)
{
// ReplaceLocalhost only returns null when its input is null; the foreach variable is non-null.
serverConfiguration.BaseAddresses +=
Utils.ReplaceLocalhost(baseAddress)!;
}
// alternate base addresses
if (alternateBaseAddresses != null)
{
foreach (string alternateBaseAddress in alternateBaseAddresses)
{
// ReplaceLocalhost only returns null when its input is null; the foreach variable is non-null.
serverConfiguration.AlternateBaseAddresses +=
Utils.ReplaceLocalhost(alternateBaseAddress)!;
}
}
// add container for policies
serverConfiguration.SecurityPolicies = [];
// add user token policy container and Anonymous
serverConfiguration.UserTokenPolicies = [];
ApplicationConfiguration.ServerConfiguration = serverConfiguration;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerSelected AddUnsecurePolicyNone(
bool addPolicy = true)
{
if (addPolicy)
{
var policies = ApplicationConfiguration
.ServerConfiguration!
.SecurityPolicies.ToList();
InternalAddPolicy(policies, MessageSecurityMode.None, SecurityPolicies.None);
ApplicationConfiguration.ServerConfiguration!.SecurityPolicies = policies.ToArrayOf();
}
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerSelected AddSignPolicies(
bool addPolicies = true)
{
if (addPolicies)
{
AddSecurityPolicies(true, false, false);
}
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerSelected AddSignAndEncryptPolicies(
bool addPolicies = true)
{
if (addPolicies)
{
AddSecurityPolicies(false, false, false);
}
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerSelected AddEccSignPolicies()
{
AddEccSecurityPolicies(true);
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerSelected AddEccSignAndEncryptPolicies()
{
AddEccSecurityPolicies(false);
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerSelected AddPolicy(
MessageSecurityMode securityMode,
string securityPolicy)
{
if (!SecurityPolicies.IsValidSecurityPolicyUri(securityPolicy))
{
throw new ArgumentException("Unknown security policy", nameof(securityPolicy));
}
if (securityMode == MessageSecurityMode.None ||
securityPolicy.Equals(SecurityPolicies.None, StringComparison.Ordinal))
{
throw new ArgumentException("Use AddUnsecurePolicyNone to add no security policy.");
}
var policies = ApplicationConfiguration.ServerConfiguration!.SecurityPolicies.ToList();
InternalAddPolicy(
policies,
securityMode,
securityPolicy);
ApplicationConfiguration.ServerConfiguration!.SecurityPolicies = policies.ToArrayOf();
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerSelected AddUserTokenPolicy(
UserTokenType userTokenType)
{
ApplicationConfiguration.ServerConfiguration!.UserTokenPolicies +=
new UserTokenPolicy(userTokenType);
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerSelected AddUserTokenPolicy(
UserTokenPolicy userTokenPolicy)
{
ApplicationConfiguration.ServerConfiguration!.UserTokenPolicies += userTokenPolicy ?? throw new ArgumentNullException(nameof(userTokenPolicy));
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderSecurityOptions SetApplicationCertificates(
ArrayOf<CertificateIdentifier> certIdList)
{
ApplicationConfiguration.SecurityConfiguration.ApplicationCertificates = certIdList;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderSecurityOptions SetMaxRejectedCertificates(
int maxRejectedCertificates)
{
ApplicationConfiguration.SecurityConfiguration.MaxRejectedCertificates
= maxRejectedCertificates;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderSecurityOptions SetAutoAcceptUntrustedCertificates(
bool autoAccept)
{
ApplicationConfiguration.SecurityConfiguration.AutoAcceptUntrustedCertificates
= autoAccept;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderSecurityOptions SetAddAppCertToTrustedStore(
bool addToTrustedStore)
{
ApplicationConfiguration.SecurityConfiguration.AddAppCertToTrustedStore
= addToTrustedStore;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderSecurityOptions SetRejectSHA1SignedCertificates(
bool rejectSHA1Signed)
{
ApplicationConfiguration.SecurityConfiguration.RejectSHA1SignedCertificates
= rejectSHA1Signed;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderSecurityOptions SetRejectUnknownRevocationStatus(
bool rejectUnknownRevocationStatus)
{
ApplicationConfiguration.SecurityConfiguration.RejectUnknownRevocationStatus =
rejectUnknownRevocationStatus;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderSecurityOptions SetUseValidatedCertificates(
bool useValidatedCertificates)
{
ApplicationConfiguration.SecurityConfiguration.UseValidatedCertificates
= useValidatedCertificates;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderSecurityOptions SetSuppressNonceValidationErrors(
bool suppressNonceValidationErrors)
{
ApplicationConfiguration.SecurityConfiguration.SuppressNonceValidationErrors =
suppressNonceValidationErrors;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderSecurityOptions SetSendCertificateChain(
bool sendCertificateChain)
{
ApplicationConfiguration.SecurityConfiguration.SendCertificateChain
= sendCertificateChain;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderSecurityOptions SetMinimumCertificateKeySize(
ushort keySize)
{
ApplicationConfiguration.SecurityConfiguration.MinimumCertificateKeySize = keySize;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderSecurityOptions AddCertificatePasswordProvider(
ICertificatePasswordProvider certificatePasswordProvider)
{
ApplicationConfiguration.SecurityConfiguration.CertificatePasswordProvider
= certificatePasswordProvider;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderTransportQuotasSet SetTransportQuotas(
TransportQuotas transportQuotas)
{
ApplicationConfiguration.TransportQuotas = transportQuotas;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderTransportQuotas SetOperationTimeout(
int operationTimeout)
{
ApplicationConfiguration.TransportQuotas!.OperationTimeout = operationTimeout;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderTransportQuotas SetMaxStringLength(
int maxStringLength)
{
ApplicationConfiguration.TransportQuotas!.MaxStringLength = maxStringLength;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderTransportQuotas SetMaxByteStringLength(
int maxByteStringLength)
{
ApplicationConfiguration.TransportQuotas!.MaxByteStringLength = maxByteStringLength;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderTransportQuotas SetMaxArrayLength(int maxArrayLength)
{
ApplicationConfiguration.TransportQuotas!.MaxArrayLength = maxArrayLength;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderTransportQuotas SetMaxMessageSize(int maxMessageSize)
{
ApplicationConfiguration.TransportQuotas!.MaxMessageSize = maxMessageSize;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderTransportQuotas SetMaxBufferSize(int maxBufferSize)
{
ApplicationConfiguration.TransportQuotas!.MaxBufferSize = maxBufferSize;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderTransportQuotas SetChannelLifetime(
int channelLifetime)
{
ApplicationConfiguration.TransportQuotas!.ChannelLifetime = channelLifetime;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderTransportQuotas SetSecurityTokenLifetime(
int securityTokenLifetime)
{
ApplicationConfiguration.TransportQuotas!.SecurityTokenLifetime = securityTokenLifetime;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderTransportQuotas SetMaxEncodingNestingLevels(
int maxEncodingNestingLevels)
{
ApplicationConfiguration.TransportQuotas!.MaxEncodingNestingLevels
= maxEncodingNestingLevels;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderTransportQuotas SetMaxDecoderRecoveries(
int maxDecoderRecoveries)
{
ApplicationConfiguration.TransportQuotas!.MaxDecoderRecoveries = maxDecoderRecoveries;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMinRequestThreadCount(
int minRequestThreadCount)
{
ApplicationConfiguration.ServerConfiguration!.MinRequestThreadCount
= minRequestThreadCount;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxRequestThreadCount(
int maxRequestThreadCount)
{
ApplicationConfiguration.ServerConfiguration!.MaxRequestThreadCount
= maxRequestThreadCount;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxQueuedRequestCount(
int maxQueuedRequestCount)
{
ApplicationConfiguration.ServerConfiguration!.MaxQueuedRequestCount
= maxQueuedRequestCount;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetDiagnosticsEnabled(
bool diagnosticsEnabled)
{
ApplicationConfiguration.ServerConfiguration!.DiagnosticsEnabled = diagnosticsEnabled;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxSessionCount(int maxSessionCount)
{
ApplicationConfiguration.ServerConfiguration!.MaxSessionCount = maxSessionCount;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxChannelCount(int maxChannelCount)
{
ApplicationConfiguration.ServerConfiguration!.MaxChannelCount = maxChannelCount;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMinSessionTimeout(
int minSessionTimeout)
{
ApplicationConfiguration.ServerConfiguration!.MinSessionTimeout = minSessionTimeout;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxSessionTimeout(
int maxSessionTimeout)
{
ApplicationConfiguration.ServerConfiguration!.MaxSessionTimeout = maxSessionTimeout;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxBrowseContinuationPoints(
int maxBrowseContinuationPoints)
{
ApplicationConfiguration.ServerConfiguration!.MaxBrowseContinuationPoints
= maxBrowseContinuationPoints;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxQueryContinuationPoints(
int maxQueryContinuationPoints)
{
ApplicationConfiguration.ServerConfiguration!.MaxQueryContinuationPoints
= maxQueryContinuationPoints;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxHistoryContinuationPoints(
int maxHistoryContinuationPoints)
{
ApplicationConfiguration.ServerConfiguration!.MaxHistoryContinuationPoints
= maxHistoryContinuationPoints;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxRequestAge(int maxRequestAge)
{
ApplicationConfiguration.ServerConfiguration!.MaxRequestAge = maxRequestAge;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMinPublishingInterval(
int minPublishingInterval)
{
ApplicationConfiguration.ServerConfiguration!.MinPublishingInterval
= minPublishingInterval;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxPublishingInterval(
int maxPublishingInterval)
{
ApplicationConfiguration.ServerConfiguration!.MaxPublishingInterval
= maxPublishingInterval;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetPublishingResolution(
int publishingResolution)
{
ApplicationConfiguration.ServerConfiguration!.PublishingResolution
= publishingResolution;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxSubscriptionLifetime(
int maxSubscriptionLifetime)
{
ApplicationConfiguration.ServerConfiguration!.MaxSubscriptionLifetime
= maxSubscriptionLifetime;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxMessageQueueSize(
int maxMessageQueueSize)
{
ApplicationConfiguration.ServerConfiguration!.MaxMessageQueueSize = maxMessageQueueSize;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxNotificationQueueSize(
int maxNotificationQueueSize)
{
ApplicationConfiguration.ServerConfiguration!.MaxNotificationQueueSize
= maxNotificationQueueSize;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxNotificationsPerPublish(
int maxNotificationsPerPublish)
{
ApplicationConfiguration.ServerConfiguration!.MaxNotificationsPerPublish
= maxNotificationsPerPublish;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMinMetadataSamplingInterval(
int minMetadataSamplingInterval)
{
ApplicationConfiguration.ServerConfiguration!.MinMetadataSamplingInterval
= minMetadataSamplingInterval;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetAvailableSamplingRates(
ArrayOf<SamplingRateGroup> availableSampleRates)
{
ApplicationConfiguration.ServerConfiguration!.AvailableSamplingRates
= availableSampleRates;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetRegistrationEndpoint(
EndpointDescription registrationEndpoint)
{
ApplicationConfiguration.ServerConfiguration!.RegistrationEndpoint
= registrationEndpoint;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxRegistrationInterval(
int maxRegistrationInterval)
{
ApplicationConfiguration.ServerConfiguration!.MaxRegistrationInterval
= maxRegistrationInterval;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetNodeManagerSaveFile(
string nodeManagerSaveFile)
{
ApplicationConfiguration.ServerConfiguration!.NodeManagerSaveFile = nodeManagerSaveFile;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMinSubscriptionLifetime(
int minSubscriptionLifetime)
{
ApplicationConfiguration.ServerConfiguration!.MinSubscriptionLifetime
= minSubscriptionLifetime;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxPublishRequestCount(
int maxPublishRequestCount)
{
ApplicationConfiguration.ServerConfiguration!.MaxPublishRequestCount
= maxPublishRequestCount;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxSubscriptionCount(
int maxSubscriptionCount)
{
ApplicationConfiguration.ServerConfiguration!.MaxSubscriptionCount
= maxSubscriptionCount;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxEventQueueSize(
int setMaxEventQueueSize)
{
ApplicationConfiguration.ServerConfiguration!.MaxEventQueueSize = setMaxEventQueueSize;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions AddServerProfile(string serverProfile)
{
ApplicationConfiguration.ServerConfiguration!.ServerProfileArray += serverProfile;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetShutdownDelay(int shutdownDelay)
{
ApplicationConfiguration.ServerConfiguration!.ShutdownDelay = shutdownDelay;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions AddServerCapabilities(
string serverCapability)
{
ApplicationConfiguration.ServerConfiguration!.ServerCapabilities += serverCapability;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetSupportedPrivateKeyFormats(
ArrayOf<string> supportedPrivateKeyFormats)
{
ApplicationConfiguration.ServerConfiguration!.SupportedPrivateKeyFormats
= supportedPrivateKeyFormats;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxTrustListSize(
int maxTrustListSize)
{
ApplicationConfiguration.ServerConfiguration!.MaxTrustListSize = maxTrustListSize;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMultiCastDnsEnabled(
bool multiCastDnsEnabled)
{
ApplicationConfiguration.ServerConfiguration!.MultiCastDnsEnabled = multiCastDnsEnabled;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetReverseConnect(
ReverseConnectServerConfiguration reverseConnectConfiguration)
{
ApplicationConfiguration.ServerConfiguration!.ReverseConnect
= reverseConnectConfiguration;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetOperationLimits(
OperationLimits operationLimits)
{
ApplicationConfiguration.ServerConfiguration!.OperationLimits = operationLimits;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetAuditingEnabled(
bool auditingEnabled)
{
ApplicationConfiguration.ServerConfiguration!.AuditingEnabled = auditingEnabled;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetHttpsMutualTls(
bool mTlsEnabled)
{
ApplicationConfiguration.ServerConfiguration!.HttpsMutualTls = mTlsEnabled;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderClientOptions SetDefaultSessionTimeout(
int defaultSessionTimeout)
{
ApplicationConfiguration.ClientConfiguration!.DefaultSessionTimeout
= defaultSessionTimeout;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderClientOptions AddWellKnownDiscoveryUrls(
string wellKnownDiscoveryUrl)
{
ApplicationConfiguration.ClientConfiguration!.WellKnownDiscoveryUrls += wellKnownDiscoveryUrl;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderClientOptions AddDiscoveryServer(
EndpointDescription discoveryServer)
{
ApplicationConfiguration.ClientConfiguration!.DiscoveryServers += discoveryServer;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderClientOptions SetEndpointCacheFilePath(
string endpointCacheFilePath)
{
ApplicationConfiguration.ClientConfiguration!.EndpointCacheFilePath
= endpointCacheFilePath;
return this;
}
/// <inheritdoc/>
IApplicationConfigurationBuilderClientOptions IApplicationConfigurationBuilderClientOptions.SetMinSubscriptionLifetime(
int minSubscriptionLifetime)
{
ApplicationConfiguration.ClientConfiguration!.MinSubscriptionLifetime
= minSubscriptionLifetime;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderClientOptions SetReverseConnect(
ReverseConnectClientConfiguration reverseConnect)
{
ApplicationConfiguration.ClientConfiguration!.ReverseConnect = reverseConnect;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderClientOptions SetClientOperationLimits(
OperationLimits operationLimits)
{
ApplicationConfiguration.ClientConfiguration!.OperationLimits = operationLimits;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderTraceConfiguration SetOutputFilePath(
string outputFilePath)
{
ApplicationConfiguration.TraceConfiguration!.OutputFilePath = outputFilePath;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderTraceConfiguration SetDeleteOnLoad(bool deleteOnLoad)
{
ApplicationConfiguration.TraceConfiguration!.DeleteOnLoad = deleteOnLoad;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderTraceConfiguration SetTraceMasks(int traceMasks)
{
ApplicationConfiguration.TraceConfiguration!.TraceMasks = traceMasks;
return this;
}
/// <inheritdoc/>
[Experimental("UA_NETStandard_1")]
public IApplicationConfigurationBuilderExtension AddExtension<T>(
XmlQualifiedName elementName,
T value,
Action<IEncoder, T> encoderFunc)
{
ApplicationConfiguration.UpdateExtension(elementName, value, encoderFunc);
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderExtension AddExtension<T>(
XmlQualifiedName? elementName,
T value)
where T : IEncodeable
{
ApplicationConfiguration.UpdateExtension(elementName, value);
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetDurableSubscriptionsEnabled(
bool durableSubscriptionsEnabled)
{
ApplicationConfiguration.ServerConfiguration!.DurableSubscriptionsEnabled
= durableSubscriptionsEnabled;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxDurableNotificationQueueSize(
int maxDurableNotificationQueueSize)
{
ApplicationConfiguration.ServerConfiguration!.MaxDurableNotificationQueueSize =
maxDurableNotificationQueueSize;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxDurableEventQueueSize(
int maxDurableEventQueueSize)
{
ApplicationConfiguration.ServerConfiguration!.MaxDurableEventQueueSize
= maxDurableEventQueueSize;
return this;
}
/// <inheritdoc/>
public IApplicationConfigurationBuilderServerOptions SetMaxDurableSubscriptionLifetime(
int maxDurableSubscriptionLifetimeInHours)
{
ApplicationConfiguration.ServerConfiguration!.MaxDurableSubscriptionLifetimeInHours =
maxDurableSubscriptionLifetimeInHours;
return this;
}
/// <summary>
/// Create ApplicationCertificates from a PKI root.
/// </summary>
/// <param name="subjectName">The subject name.</param>
/// <param name="storeType">The cert store type: ex: "Directory"</param>
/// <param name="storePath">The PKI root.</param>
/// <returns>The application certificates.</returns>
public static ArrayOf<CertificateIdentifier> CreateDefaultApplicationCertificates(
string subjectName,
string? storeType = null,
string? storePath = null)
{
var certificateIdentifiers = new List<CertificateIdentifier>
{
new() {
StoreType = storeType,
StorePath = storePath,
SubjectName = subjectName,
CertificateType = ObjectTypeIds.RsaSha256ApplicationCertificateType
},
new() {
StoreType = storeType,
StorePath = storePath,
SubjectName = subjectName,
CertificateType = ObjectTypeIds.EccNistP256ApplicationCertificateType
},
new() {
StoreType = storeType,
StorePath = storePath,
SubjectName = subjectName,
CertificateType = ObjectTypeIds.EccNistP384ApplicationCertificateType
}
};
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
certificateIdentifiers.AddRange(
[
new CertificateIdentifier
{
StoreType = storeType,
StorePath = storePath,
SubjectName = subjectName,
CertificateType = ObjectTypeIds
.EccBrainpoolP256r1ApplicationCertificateType
},
new CertificateIdentifier
{
StoreType = storeType,
StorePath = storePath,
SubjectName = subjectName,
CertificateType = ObjectTypeIds
.EccBrainpoolP384r1ApplicationCertificateType
}
]);
}
return certificateIdentifiers.ToArrayOf();
}
/// <summary>
/// Internal enumeration of supported trust lists.
/// </summary>
private enum TrustlistType
{
Application,
Trusted,
Issuer,
TrustedHttps,
IssuerHttps,
TrustedUser,
IssuerUser,
Rejected
}
/// <summary>
/// Return the default PKI root path if root is unspecified, directory or X509Store.
/// </summary>
/// <param name="root">A real root path or the store type.</param>
private static string DefaultPKIRoot(string? root)
{
if (root == null ||
root.Equals(CertificateStoreType.Directory, StringComparison.OrdinalIgnoreCase))
{
return CertificateStoreIdentifier.DefaultPKIRoot;
}
else if (root.Equals(
CertificateStoreType.X509Store,
StringComparison.OrdinalIgnoreCase))
{
return CertificateStoreIdentifier.CurrentUser;
}
return root;
}
/// <summary>
/// Determine the default store path for a given trust list type.
/// </summary>
/// <param name="trustListType">The trust list type.</param>
/// <param name="pkiRoot">A PKI root for which the store path is needed.</param>
/// <exception cref="NotSupportedException"></exception>
private static string DefaultCertificateStorePath(
TrustlistType trustListType,
string pkiRoot)
{
string pkiRootType = CertificateStoreIdentifier.DetermineStoreType(pkiRoot);
if (pkiRootType.Equals(
CertificateStoreType.Directory,
StringComparison.OrdinalIgnoreCase))
{
// see https://reference.opcfoundation.org/v104/GDS/docs/F.1/
string leafPath;
switch (trustListType)
{
case TrustlistType.Application:
leafPath = "own";
break;
case TrustlistType.Trusted:
leafPath = "trusted";
break;
case TrustlistType.Issuer:
leafPath = "issuer";
break;
case TrustlistType.TrustedHttps:
leafPath = "trustedHttps";
break;
case TrustlistType.IssuerHttps:
leafPath = "issuerHttps";
break;
case TrustlistType.TrustedUser:
leafPath = "trustedUser";
break;
case TrustlistType.IssuerUser:
leafPath = "issuerUser";
break;
case TrustlistType.Rejected:
leafPath = "rejected";
break;
default:
leafPath = string.Empty;
break;
}
// Caller may have already provided the leaf path, then no need to add.
int startIndex = pkiRoot.Length - leafPath.Length;
char lastChar = pkiRoot[^1];
if (lastChar == Path.DirectorySeparatorChar ||
lastChar == Path.AltDirectorySeparatorChar)
{
startIndex--;
}
if (startIndex > 0 &&
pkiRoot
.Substring(startIndex, leafPath.Length)
.Equals(leafPath, StringComparison.OrdinalIgnoreCase))
{
return pkiRoot;
}
return Path.Combine(pkiRoot, leafPath);
}
else if (pkiRootType.Equals(
CertificateStoreType.X509Store,
StringComparison.OrdinalIgnoreCase))
{
switch (trustListType)
{
case TrustlistType.Application:
#if !NETFRAMEWORK
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
pkiRoot.StartsWith(
CertificateStoreIdentifier.CurrentUser,
StringComparison.OrdinalIgnoreCase))
{
return pkiRoot + "My";
}
#endif
return pkiRoot + "UA_MachineDefault";
case TrustlistType.Trusted:
return pkiRoot + "UA_Trusted";
case TrustlistType.Issuer:
return pkiRoot + "UA_Issuer";
case TrustlistType.TrustedHttps:
return pkiRoot + "UA_Trusted_Https";
case TrustlistType.IssuerHttps:
return pkiRoot + "UA_Issuer_Https";
case TrustlistType.TrustedUser:
return pkiRoot + "UA_Trusted_User";
case TrustlistType.IssuerUser:
return pkiRoot + "UA_Issuer_User";
case TrustlistType.Rejected:
return pkiRoot + "UA_Rejected";
default:
throw new NotSupportedException("Unsupported store type.");
}
}
else
{
// return custom root store
return pkiRoot;
}
}
/// <summary>
/// Add specified groups of security policies and security modes.
/// </summary>
/// <param name="includeSign">Include the Sign only policies.</param>
/// <param name="deprecated">Include the deprecated policies.</param>
/// <param name="policyNone">Include policy 'None'. (no security!)</param>
private void AddSecurityPolicies(
bool includeSign = false,
bool deprecated = false,
bool policyNone = false)
{
// create list of supported policies
List<string> defaultPolicyUris = [.. SecurityPolicies
.GetDefaultUris()];
if (deprecated)
{
defaultPolicyUris.AddRange(SecurityPolicies.GetDefaultDeprecatedUris());
}
#if NET5_0_OR_GREATER
foreach (MessageSecurityMode securityMode in Enum.GetValues<MessageSecurityMode>())
#else
foreach (MessageSecurityMode securityMode in typeof(MessageSecurityMode)
.GetEnumValues())
#endif
{
var policies = ApplicationConfiguration
.ServerConfiguration!
.SecurityPolicies.ToList();
if (policyNone && securityMode == MessageSecurityMode.None)
{
InternalAddPolicy(policies, MessageSecurityMode.None, SecurityPolicies.None);
}
else if (securityMode >= MessageSecurityMode.SignAndEncrypt ||
(includeSign && securityMode == MessageSecurityMode.Sign))
{
foreach (string policyUri in defaultPolicyUris)
{
InternalAddPolicy(policies, securityMode, policyUri);
}
}
ApplicationConfiguration.ServerConfiguration!.SecurityPolicies = policies.ToArrayOf();
}
}
/// <summary>
/// Add default Ecc policies.
/// </summary>
private void AddEccSecurityPolicies(bool sign = false)
{
// create list of supported policies
string[] defaultPolicyUris = SecurityPolicies.GetDefaultEccUris();
MessageSecurityMode securityMode = sign
? MessageSecurityMode.Sign
: MessageSecurityMode.SignAndEncrypt;
{
var policies = ApplicationConfiguration
.ServerConfiguration!
.SecurityPolicies.ToList();
foreach (string policyUri in defaultPolicyUris)
{
InternalAddPolicy(policies, securityMode, policyUri);
}
ApplicationConfiguration.ServerConfiguration!.SecurityPolicies = policies.ToArrayOf();
}
}
/// <summary>
/// Set secure defaults for flags.
/// </summary>
private static void SetSecureDefaults(SecurityConfiguration securityConfiguration)
{
// ensure secure default settings
securityConfiguration.AutoAcceptUntrustedCertificates = false;
securityConfiguration.AddAppCertToTrustedStore = false;
securityConfiguration.RejectSHA1SignedCertificates = true;
securityConfiguration.RejectUnknownRevocationStatus = true;
securityConfiguration.SuppressNonceValidationErrors = false;
securityConfiguration.SendCertificateChain = true;
securityConfiguration.MinimumCertificateKeySize = CertificateFactory.DefaultKeySize;
securityConfiguration.MaxRejectedCertificates = 5;
}
/// <summary>
/// Add security policy if it doesn't exist yet.
/// </summary>
/// <param name="policies">The list to which the policies are added.</param>
/// <param name="securityMode">The message security mode.</param>
/// <param name="policyUri">The security policy Uri.</param>
/// <exception cref="ArgumentException"><paramref name="securityMode"/></exception>
private static bool InternalAddPolicy(
List<ServerSecurityPolicy> policies,
MessageSecurityMode securityMode,
string policyUri)
{
if (securityMode == MessageSecurityMode.Invalid)
{
throw new ArgumentException("Invalid security mode selected", nameof(securityMode));
}
var newPolicy = new ServerSecurityPolicy
{
SecurityMode = securityMode,
SecurityPolicyUri = policyUri
};
if (policies.Find(s =>
s.SecurityMode == newPolicy.SecurityMode &&
string.Equals(
s.SecurityPolicyUri,
newPolicy.SecurityPolicyUri,
StringComparison.Ordinal)
) == null)
{
policies.Add(newPolicy);
return true;
}
return false;
}
private bool m_typeSelected;
}
}