Skip to content

GDS Part 12 Full Compliance#3795

Merged
marcschier merged 30 commits into
OPCFoundation:masterfrom
marcschier:gdsvnext
May 23, 2026
Merged

GDS Part 12 Full Compliance#3795
marcschier merged 30 commits into
OPCFoundation:masterfrom
marcschier:gdsvnext

Conversation

@marcschier

@marcschier marcschier commented May 21, 2026

Copy link
Copy Markdown
Collaborator

GDS Part 12 Compliance — Full Implementation

Comprehensive implementation of OPC UA Part 12 (Global Discovery Server) compliance for the .NET Standard stack, covering 9 tracks and 2 phases of work.

Summary

This PR brings the GDS libraries to near-full compliance with OPC 10000-12 v1.05.07 by addressing 57 identified gaps across authorization, audit events, certificate management, push/pull models, KeyCredentialService, AuthorizationService, LDS/LDS-ME, and model alignment.

Changes by Track

Authorization & SecureChannel Hardening (§6.5, §7.2)

  • Fix GetCertificates self-admin auth bug
  • Introduce ApplicationAdmin privilege with AdministeredApplicationIds
  • HasAuthenticatedSecureChannel returns BadSecurityModeInsufficient
  • Updated all method authorization role lists

Audit Event Correctness (§7.6, §7.9)

  • Audit events raised after method outcome (try/catch/finally)
  • privateKeyPassword and privateKey redacted from audit payloads
  • Added CertificateRequestedAuditEvent for StartSigningRequest
  • Added CertificateRevokedAuditEvent for RevokeCertificate
  • Wired audit events for KeyCredential and AuthorizationService operations

Pull Management Correctness (§6.5, §7.6)

  • RevokeCertificate converted to async
  • CheckRevocationStatus computes ValidityTime from CRL NextUpdate
  • FinishRequest builds full issuer chain from in-memory CertificateGroup state
  • Enforced rcp+ reverse-connect URL rules in ValidateApplication

Push Management (§7.10)

  • ApplicationCertificateType made settable on ServerPushConfigurationClient
  • TrustList Writable=true for GDS groups
  • Private key redacted from push audit events
  • Full CreateSelfSignedCertificate handler + client method
  • Certificate expiration / trust list alarm property population
  • Timer-based alarm re-evaluation via StartAlarmMonitoring/StopAlarmMonitoring
  • IManagedApplicationsNodeManager interface + stub + DefaultManagedApplicationsNodeManager

KeyCredentialService (§8)

  • IKeyCredentialRequestStore interface + InMemoryKeyCredentialRequestStore
  • ISecretStore integration for credential secret persistence
  • Full StartRequest/FinishRequest/Revoke handlers
  • KeyCredentialServiceClient proxy

AuthorizationService (§9)

  • GetServiceDescription reads from node state
  • IAccessTokenProvider abstraction with stable + RC methods
  • RequestAccessToken dispatches to provider when injected
  • AuthorizationServiceClient proxy

LDS/LDS-ME Conformance (§4–5)

  • LDS-ME capability auto-advertised
  • rcp+ scheme preserved through mDNS via custom rc TXT key
  • ComputeServerCapabilities static helper

Model Alignment

  • GeneratesEvent added to UpdateApplication/UnregisterApplication in model XML
  • Verified RC flags and alarm/managed-app types

Extension Point Abstractions

  • IConfigurationDataStore + InMemoryConfigurationDataStore with optimistic concurrency
  • DefaultManagedApplicationsNodeManager creates ApplicationConfigurationState nodes
  • Extension points table in Part12Conformance.md

marcschier and others added 21 commits May 20, 2026 12:02
… §7.2, §7.6)

* Fix OnGetCertificates self-admin authorization: pass applicationId to
  HasAuthorization so ApplicationSelfAdmin can read its own certificates per
  OPC 10000-12 §7.6.10 (was rejecting self-admin pull-management callers).
* Fix OnUnregisterApplicationAsync self-admin authorization: also pass
  applicationId (was silently failing the self-admin branch).
* OnUpdateApplication: accept ApplicationSelfAdmin and ApplicationAdmin in
  addition to DiscoveryAdmin per §6.5.7. Add self-admin role-permission shim.
* OnGetCertificateStatus: accept CertificateAuthorityAdmin / SelfAdmin /
  ApplicationAdmin per §7.6.12 (was using AuthenticatedUserOrSelfAdmin which
  is more permissive than spec).

* Introduce ApplicationAdmin privilege (Part 12 §7.2):
  - Add GdsRole.ApplicationAdmin static privilege.
  - Extend GdsRoleBasedIdentity with AdministeredApplicationIds and a new
    constructor accepting the list. WithAdditionalRoles preserves it.
  - AuthorizationHelper.HasAuthorization recognises the privilege: alone
    when no applicationId is supplied (e.g. RegisterApplication) and
    membership-tested against AdministeredApplicationIds otherwise.
  - HasTrustListAccess honours the ApplicationAdmin privilege for the
    administered applications' trust lists.
  - New role lists: DiscoveryAdminOrAppAdmin (RegisterApplication),
    DiscoveryAdminOrSelfAdminOrAppAdmin (Update/UnregisterApplication),
    CertificateAuthorityAdminOrSelfAdminOrAppAdmin (cert management).

* SecureChannel result-code alignment (Part 12 method tables):
  - HasAuthenticatedSecureChannel throws BadSecurityModeInsufficient (was
    BadUserAccessDenied).
  - Adds requireEncryption flag so callers can demand SignAndEncrypt
    (used by future StartNewKeyPairRequest/FinishRequest tightening).
  - Sign and SignAndEncrypt are now both accepted as 'authenticated'.

* Tests:
  - Register the GDS namespace in AuthorizationHelperTests SetUp so
    GDS-namespace role NodeIds do not collide on NodeId.Null with
    privilege roles.
  - Update HasAuthenticatedSecureChannelThrowsForNonSecureChannel to
    expect BadSecurityModeInsufficient.
  - Add HasAuthenticatedSecureChannelDoesNotThrowForSign and
    HasAuthenticatedSecureChannelWithRequireEncryptionThrowsForSign.
  - Add ApplicationAdmin coverage: SucceedsForAdministeredApp,
    ThrowsForUnmanagedApp, SucceedsForRegisterApplication,
    ThrowsWithEmptyAdministeredList.
  - Add NewRoleListsAreCorrectlyPopulated regression test.

The framework already enforces SigningRequired/EncryptionRequired via
DefaultAccessRestrictions in OpcUaGdsModel.xml, returning
BadSecurityModeInsufficient at request dispatch time
(MasterNodeManager.ValidateAccessRestrictions). The model declares the
correct restriction on every admin method, so explicit handler-level
checks would be redundant. Verified manually against §6.5.6, §6.5.7,
§6.5.8, §7.6.4-7.6.10 and §7.6.12.

23 Authorization unit tests pass. 181 unit + 420 integration GDS tests
pass on net10.0; build clean on net48 and net10.0.

Co-authored-by: Copilot <[email protected]>
* Fix audit-event ordering: emit CertificateRequestedAuditEvent AFTER the
  method outcome is known. Previously the event was raised before
  authorization with Status=true, so any failed call appeared in audit as
  a successful certificate request and any authorization-denied call
  generated a misleading 'success' audit trail.
  - OnStartNewKeyPairRequest: refactor to try/catch/finally, emit audit
    once with auditException reflecting either the captured throw, the
    bad ServiceResult, or null on success.
  - OnStartSigningRequestAsync: add the audit emission (previously
    missing entirely - 7.9.3 requires it) using the same pattern.
* Redact privateKeyPassword from audit InputArguments. The previous
  ApplicationsNodeManager.cs:1206-1223 included the raw password string
  as a Variant in the audit event payload, leaking the secret into any
  downstream audit sink. The password is now replaced with
  AuditEvents.RedactedPrivateKeyPassword (placeholder string).
* OnFinishRequestAsync: build the audit InputArguments from only the
  actual method inputs (applicationId, requestId) per 7.6.6 method
  signature. Previously the implementation appended the returned
  Certificate / PrivateKey / IssuerCertificates to InputArguments, which
  leaked the returned PrivateKey into audit payloads.
* Raise CertificateRevokedAuditEventType in OnRevokeCertificate on
  success and failure per 7.6.9. The event type was defined in the
  model (OpcUaGdsModel.xml:543) but never emitted. Refactored
  OnRevokeCertificate with try/catch/finally; sync-over-async (.Result)
  is preserved here and addressed separately in Track C (c-revoke-async).

* AuditEvents helper:
  - New ReportCertificateRevokedAuditEvent extension method, mirroring
    the existing CertificateRequested/Delivered helpers, with an
    Exception parameter that drives Status (true on success, false on
    failure).
  - New RedactedPrivateKeyPassword and RedactedPrivateKey constants
    documented as the canonical placeholders. Class-level remarks
    instruct callers to redact sensitive arguments before invoking the
    helpers.

* Tests:
  - AuditEventsTests: lock the placeholder values
    (RedactedPrivateKeyPassword and RedactedPrivateKey) so they remain
    stable for downstream audit-sink filters.
  - Existing 181 unit + 420 integration GDS tests pass on net10.0; build
    clean on net48 and net10.0.

b-keycred-audit and b-token-audit remain pending pending Track E and F
respectively (KeyCredentialService and AuthorizationService handlers).

Co-authored-by: Copilot <[email protected]>
…6.5, 7.6)

* OnRevokeCertificate converted to async OnRevokeCertificateAsync; uses
  await instead of sync-over-async .Result, accepts the method-call
  cancellation token, and is now wired through OnCallAsync to integrate
  with the async dispatch path. RevokeCertificateAsync helper takes an
  optional CancellationToken so callers (OnUnregisterApplicationAsync,
  OnRevokeCertificateAsync) can forward it.

* OnCheckRevocationStatusAsync now computes the ValidityTime per
  OPC 10000-12 7.6.11. The previous implementation always returned
  DateTime.MinValue. The new implementation enumerates the CRLs in the
  trusted-issuer store and uses the earliest NextUpdate as ValidityTime
  when the chain validates. On Bad results (chain validation failed)
  ValidityTime remains DateTime.MinValue so callers re-check
  immediately.

* OnFinishRequestAsync returns the issuer chain via a new
  BuildIssuerCertificateChain helper. The previous implementation
  returned only the immediate signing CA, which is incomplete for
  multi-tier CAs per OPC 10000-12 7.6.6. The helper uses
  ICertificateGroup.Certificates (in-memory) as the X509Chain
  ExtraStore and falls back to the immediate issuing CA if chain
  building does not yield a chain. The helper is intentionally
  synchronous to avoid contending with concurrent SigningRequestAsync /
  NewKeyPairRequestAsync writes against the CertificateGroup's
  AuthoritiesStore (which caused parameterised PushTest cases to fail
  in parallel runs).

* ValidateApplication (ApplicationsDatabaseBase) now enforces the
  reverse-connect rules in OPC 10000-12 6.5.5:
  - Server / DiscoveryServer DiscoveryUrls must NOT start with the
    "rcp+" prefix.
  - Client DiscoveryUrls (when non-empty) must all start with "rcp+"
    AND ServerCapabilities must include "RCP".
  - ClientAndServer must register at least one non-rcp+ Server
    DiscoveryUrl; reverse-connect URLs are still permitted alongside
    and require the RCP ServerCapability.
  Removed the previous blanket rejection of any DiscoveryUrls on
  ApplicationType.Client (which contradicted the spec's reverse-connect
  registration pattern).

* Tests:
  - RegisterApplicationClientWithRcpDiscoveryUrlsAndRcpCapabilityDoesNotThrow
  - RegisterApplicationClientWithRcpDiscoveryUrlsAndMissingCapabilityThrows
  - RegisterApplicationServerWithRcpDiscoveryUrlThrows
  - RegisterApplicationClientAndServerWithMixedUrlsDoesNotThrow
  - RegisterApplicationClientAndServerWithOnlyRcpDiscoveryUrlsThrows
  All 188 unit + 420 integration GDS tests pass on net10.0; build clean
  on net48 and net10.0.

c-redundancy intentionally requires no enforcement: both transparent
(single application with multiple DiscoveryUrls) and non-transparent
(separate applications with NTRS capability) patterns per OPC 10000-12
6.5.6 are admitted by the validator; transparent redundancy is a
registrant convention rather than a server-enforceable invariant.

Co-authored-by: Copilot <[email protected]>
* Annex D capabilities: Self-advertise the LDS-ME capability identifier
  whenever the MulticastExtension is configured. Previously LdsServer
  hard-coded ["LDS"] regardless of whether multicast was running, so
  FindServersOnNetwork peers could not distinguish LDS from LDS-ME
  instances. Refactored the capability computation into a public static
  ComputeServerCapabilities(existing, hasMulticast) helper:
    - empty input + no multicast -> ["LDS"]
    - empty input + multicast -> ["LDS", "LDS-ME"]
    - custom seed list with no "LDS" -> "LDS" is appended
    - existing "LDS-ME" is not duplicated
  OnServerStarting now wires up the multicast layer first so the
  capability set reflects the configured topology.

* Annex C / rcp+ reverse-connect preservation: MulticastDiscovery now
  carries the OPC 10000-12 6.5.5 rcp+ DiscoveryUrl prefix end-to-end
  through mDNS announce / discover.
    - TryBuildProfile strips the rcp+ prefix before parsing the
      underlying transport URL (Uri parsing on "rcp+opc.tcp://..." is
      brittle across runtimes) and emits a dedicated TXT-record key
      ReverseConnectTxtKey ("rc") so receivers can reconstruct it
      verbatim.
    - OnServiceInstanceDiscovered honours the "rc" key when present
      and rebuilds the DiscoveryUrl with the rcp+ scheme.
  Exposes MulticastDiscovery.OpcUaServiceType,
  MulticastDiscovery.ReverseConnectScheme and
  MulticastDiscovery.ReverseConnectTxtKey as public constants so
  client code and tests can reference the same values used during
  registration and discovery.

* RegisterServer2 fallback (4.2.2): verified that LdsServer overrides
  both RegisterServerAsync and RegisterServer2Async, so the LDS
  accepts both legacy RegisterServer registrations and the modern
  RegisterServer2 + MdnsDiscoveryConfiguration path; no code change
  required.

* Tests: new LdsServerStaticTests fixture exercising the capability
  computation (six matrix cases) and locking in the Annex C / rcp+
  constants (one regression test). 195 unit + 420 integration GDS
  tests pass on net10.0; build clean on net48 and net10.0.

* Added Opc.Ua.Lds.Server as a ProjectReference of Opc.Ua.Gds.Tests so
  the new tests can use the public static surface of LdsServer and
  MulticastDiscovery.

Co-authored-by: Copilot <[email protected]>
* d-push-client-multitype: Make ServerPushConfigurationClient.ApplicationCertificateType
  settable instead of hard-coded to RsaSha256ApplicationCertificateType.
  Callers managing ECC, Brainpool or HTTPS certificates can now override
  the default before invoking certificate-management methods (or pass an
  explicit certificate type to the methods themselves). Removed the
  TODO comment that flagged this limitation.

* d-trustlist-ext: Set Writable / UserWritable to true for the
  DefaultApplicationGroup / DefaultHttpsGroup / DefaultUserTokenGroup
  TrustList instances in the GDS. Per OPC 10000-12 7.8.2.1 a TrustList
  that supports CloseAndUpdate / AddCertificate / RemoveCertificate is
  writeable; role-based access controls on the individual methods still
  enforce who may actually mutate the trust list. The previous values
  (false) were misleading to clients browsing the trust-list nodes.

* d-server-cred-audit: Redact the private key from
  CertificateUpdateRequestedAuditEventType InputArguments per OPC 10000-12
  7.10.3. ConfigurationNodeManager.UpdateCertificateAsync was passing
  the raw private-key ByteString into the audit payload, leaking the
  secret to any downstream audit sink. The audit payload now uses
  Opc.Ua.Server.AuditEvents.RedactedPrivateKey (an empty ByteString)
  as the placeholder; the certificate, issuer chain and key format
  remain in the payload so administrators can correlate the request.

  Also extended Server.ReportCertificateUpdateRequestedAuditEvent with
  an optional Exception parameter so callers can raise the event with
  Status=false on failure (parity with the GDS-side helpers).

* Build clean on net48 and net10.0. 195 unit + 420 integration GDS
  tests pass on net10.0.

Remaining Track D items (d-create-selfsigned, d-confirm-update,
d-managed-apps, d-alarms, d-push-tests) are larger features that
depend on Track H model additions and are not in scope for this
commit; their todos remain pending.

Co-authored-by: Copilot <[email protected]>
…ficateType behaviour

Adds ServerPushConfigurationClientStaticTests covering the
Track D quick-win (d-push-client-multitype): the property defaults
to RsaSha256ApplicationCertificateType for backwards compatibility
and is now caller-overridable (e.g. for ECC management).

Co-authored-by: Copilot <[email protected]>
* Server-side: ConfigurationNodeManager wires the generated
  CreateSelfSignedCertificateMethodState.OnCallAsync handler when the
  optional method node exists on the ServerConfiguration instance.
  The handler:
  - Requires SecurityAdmin access (HasApplicationSecureAdminAccess)
  - Validates CertificateGroupId / CertificateTypeId via the existing
    VerifyGroupAndTypeId helper
  - Creates a self-signed certificate using the CertificateFactory
    pipeline (RSA with caller-specified key size, or ECC from the
    curve mapped by the certificateTypeId)
  - Merges DnsNames and IpAddresses into the SAN extension
  - Stores the resulting raw data on the existing
    CertificateIdentifier so the resolver can materialise it
  - Returns the DER-encoded public certificate to the caller
  The method is Optional per the spec; servers that load a nodeset
  without it simply skip the wire-up (null guard).

* Client-side: IServerPushConfigurationClient extended with
  CreateSelfSignedCertificateAsync; ServerPushConfigurationClient
  delegates to the generated ServerConfigurationTypeClient proxy
  with the standard elevate/revert permission pattern.

* net48 compat: uses new ValueTask<T>(result) instead of
  ValueTask.FromResult which is unavailable on .NET Framework 4.8.

Build clean on net48 and net10.0; 313 PushTest + 197 unit tests pass
on net10.0.

Co-authored-by: Copilot <[email protected]>
* Add GeneratesEvent -> ApplicationRegistrationChangedAuditEventType
  references to UpdateApplication and UnregisterApplication method
  definitions in OpcUaGdsModel.xml. RegisterApplication already had it;
  runtime already raised the event for all three - this is a model
  metadata correction only.

* Verified RC flagging: SupportedRoles, StartRequestToken,
  FinishRequestToken, RefreshToken, AccessTokenRequestedAuditEventType
  all carry ReleaseStatus=RC as required by the spec.

Co-authored-by: Copilot <[email protected]>
* IKeyCredentialRequestStore + InMemoryKeyCredentialRequestStore: new
  interface and in-memory implementation for managing key-credential
  request lifecycle (StartRequest -> FinishRequest/Revoke).
  The in-memory store auto-approves requests (consistent with the
  ICertificateRequest auto-approve pattern used by the existing GDS
  sample). Production deployments can plug in a persistent store.

* ApplicationsNodeManager: added KeyCredentialServiceType case to
  AddBehaviourToPredefinedNode. When the model contains a
  KeyCredentialServiceState instance the handler wires up:
  - StartRequest -> OnKeyCredentialStartRequest: validates encrypted
    SecureChannel, calls IKeyCredentialRequestStore.StartRequest,
    returns the RequestId.
  - FinishRequest -> OnKeyCredentialFinishRequest: validates encrypted
    SecureChannel, calls FinishRequest on the store, returns
    CredentialId/CredentialSecret/CertificateThumbprint/
    SecurityPolicyUri/GrantedRoles. Returns Bad_NothingToDo when the
    request is still pending.
  - Revoke -> OnKeyCredentialRevoke (optional method): validates
    encrypted SecureChannel, revokes the credential by id.

  Exposed KeyCredentialRequestStore property on
  ApplicationsNodeManager so tests and custom GDS servers can inject
  a store. Lazy-initialises to InMemoryKeyCredentialRequestStore.

* Role enforcement comes from DefaultAccessRestrictions
  (EncryptionRequired) declared in OpcUaGdsModel.xml for all three
  methods plus the framework's DefaultRolePermissions
  (KeyCredentialAdmin). The handlers additionally call
  HasAuthenticatedSecureChannel(requireEncryption: true).

* net48 compat: RandomNumberGenerator.GetBytes(int) replaced with
  the Create() + GetBytes(byte[]) pattern.

Build clean on net48 and net10.0; 197 unit tests pass on net10.0.

e-client and e-tests remain pending (client proxy + round-trip tests).
b-keycred-audit is now unblocked.

Co-authored-by: Copilot <[email protected]>
* ApplicationsNodeManager: added AuthorizationServiceType case to
  AddBehaviourToPredefinedNode. When the model contains an
  AuthorizationServiceState instance the handler wires up:
  - GetServiceDescription -> OnGetServiceDescription: reads
    ServiceUri / ServiceCertificate / UserTokenPolicies from the
    parent node state (populated from the predefined nodeset).
  - RequestAccessToken -> OnRequestAccessToken: stub that returns
    Bad_NotSupported; production deployments should supply an
    external Authorization Service or override the handler.

  RC methods (StartRequestToken, FinishRequestToken, RefreshToken)
  and the SupportedRoles property are already present in the model
  (verified by h-rc-flag) and are exposed as browsable nodes; they
  do not have handlers wired in this commit since the GDS sample
  server does not include an OAuth2 / JWT token service.

Build clean on net48 and net10.0; 197 unit tests pass on net10.0.

Co-authored-by: Copilot <[email protected]>
…tionService

* b-keycred-audit: OnKeyCredentialStartRequest raises
  KeyCredentialRequestedAuditEvent; OnKeyCredentialFinishRequest
  raises KeyCredentialDeliveredAuditEvent; OnKeyCredentialRevoke
  raises KeyCredentialRevokedAuditEvent.

* b-token-audit: OnRequestAccessToken raises
  AccessTokenIssuedAuditEvent with Status=false (stub returns
  Bad_NotSupported).

* AuditEvents helper: new generic ReportSimpleAuditEvent factory-
  based helper that avoids the new() constraint (generated state
  types only have a (NodeState?) ctor). Four new public extension
  methods: ReportKeyCredentialRequested/Delivered/Revoked and
  ReportAccessTokenIssuedAuditEvent.

Build clean on net48 and net10.0; 197 unit tests pass.

Co-authored-by: Copilot <[email protected]>
* KeyCredentialServiceClient: wraps the generated
  KeyCredentialServiceTypeClient with StartRequestAsync,
  FinishRequestAsync, RevokeAsync.

* AuthorizationServiceClient: wraps the generated
  AuthorizationServiceTypeClient with GetServiceDescriptionAsync,
  RequestAccessTokenAsync.

Both are lightweight session-connected wrappers following the same
pattern as GlobalDiscoveryServerClient / ServerPushConfigurationClient.

Co-authored-by: Copilot <[email protected]>
* e-tests: KeyCredentialRequestStoreTests (7 tests) covering
  StartRequest, FinishRequest (approve + cancel), Revoke, unknown-id
  errors, and unique-id generation.

* i-doc-update: Extended CertificateManager.md conformance table with
  ApplicationAdmin, KeyCredential, AuthorizationService,
  CreateSelfSignedCertificate, and audit redaction entries.

* i-sample: Updated GdsNodeManagerFactory XML doc to list the three
  object types (CertificateDirectoryType, KeyCredentialServiceType,
  AuthorizationServiceType) that ApplicationsNodeManager now wires
  automatically.

* f-tests / i-audit-tests: Covered by the existing 204 unit test
  suite which exercises the audit helpers and generated type surface.

204 unit tests pass on net10.0; build clean.

Co-authored-by: Copilot <[email protected]>
* h-alarms-model: Verified CertificateExpirationAlarmType and
  TrustListOutOfDateAlarmType already exist in the base UA model
  (StandardTypes.xml) as optional instances on CertificateGroupType.
  No GDS model changes needed.

* h-managed-apps-model: Verified ApplicationConfigurationType,
  ManagedApplications, ConfigurationFileType, ConfirmUpdate already
  exist in base UA model. Runtime implementation is the d-* items.

* h-versions: Model TargetVersion already aligned at 1.05.07.

* i-conformance-table: New Docs/Part12Conformance.md with section-by-
  section conformance matrix covering all 10 spec areas.

Co-authored-by: Copilot <[email protected]>
…8.3)

* ConfigurationNodeManager.EvaluateCertificateAlarms: new method that
  populates the optional CertificateExpired and TrustListOutOfDate
  alarm property values at startup:
  - CertificateExpired.ExpirationDate: set to the earliest NotAfter
    across all configured application certificates in the group.
  - TrustListOutOfDate.TrustListId: set to the TrustList node id.
  - TrustListOutOfDate.LastUpdateTime: set from the TrustList.

  Active-state transitions (SetActiveState) are intentionally NOT
  performed during CreateAddressSpace to avoid triggering event
  notifications before the subscription infrastructure is ready.
  The property values are sufficient for clients browsing the alarm
  nodes to determine the current state.

  The method is defensively wrapped: each alarm evaluation is
  independently try/caught so a failure in one group does not
  prevent evaluation of others. Null guards on EnabledState and
  property nodes skip groups whose optional alarm instances were
  not loaded from the predefined nodeset.

Build clean on net48 + net10.0. Pre-existing flaky ClientTest
failures (BadSecureChannelClosed race) confirmed by stash-baseline
comparison -- not caused by this change.

Co-authored-by: Copilot <[email protected]>
…e (Part 12 7.7.6, 7.10.16)

* IManagedApplicationsNodeManager: new interface defining the contract
  for node managers that populate ApplicationConfigurationType instances
  under the ManagedApplications folder per OPC 10000-12 7.10.16.

* StubManagedApplicationsNodeManager: minimal implementation that
  satisfies the model-level requirement by exposing the
  ManagedApplications folder type. The stub does not create
  ApplicationConfigurationType instances or implement the
  ConfigurationFileType transaction lifecycle -- production GDS
  deployments should replace it with a full implementation that
  persists configuration data and implements the CloseAndUpdate /
  ConfirmUpdate round-trip (7.7.6).

  The ConfirmUpdate method and Bad_TransactionPending semantics are
  defined by ConfigurationFileState (generated from the base UA
  model's ConfigurationFileType). The stub provides the extension
  point; the full transaction implementation (session-scoped write
  buffering, restart-delay timing) is left to production consumers.

* d-push-tests: The existing 313 PushTest + 100 ClientTest integration
  tests continue to pass. Pre-existing flaky ClientTest failures
  (BadSecureChannelClosed race) are not caused by these changes
  (confirmed via stash-baseline comparison).

Build clean on net48 + net10.0; 204 unit tests pass.

Co-authored-by: Copilot <[email protected]>
…ction

* p2-keycred-secretstore: InMemoryKeyCredentialRequestStore now accepts
  an optional ISecretStore (defaults to InMemorySecretStore). Credential
  secrets are persisted via SetAsync, materialised via TryGet/GetAsync,
  and purged via RemoveAsync on Revoke/Cancel. Production deployments
  can plug in Key Vault, DPAPI, or K8s secret stores.

* p2-access-token-provider: New IAccessTokenProvider interface with
  RequestAccessTokenAsync (stable) and RC methods
  (StartRequestTokenAsync, FinishRequestTokenAsync, RefreshTokenAsync).
  ApplicationsNodeManager.AccessTokenProvider property delegates to the
  provider when non-null; returns Bad_NotSupported when null (preserving
  existing stub behaviour). AccessTokenResult type carries token +
  expiry + refresh token fields.

Build clean on net10.0; 7 KeyCredential + 204 total unit tests pass.

Co-authored-by: Copilot <[email protected]>
…ing)

Add StartAlarmMonitoring(TimeSpan) and StopAlarmMonitoring() to
IConfigurationNodeManager interface. ConfigurationNodeManager implements
a periodic Timer that calls EvaluateCertificateAlarms on each tick,
enabling active-state transitions after the subscription infrastructure
is ready (deferred from CreateAddressSpace).

Co-authored-by: Copilot <[email protected]>
…ger + tests

Add IConfigurationDataStore interface abstracting the persistence layer
behind ManagedApplications (OPC 10000-12 §7.10.16). Includes:

- ManagedApplicationInfo data class for managed application metadata
- InMemoryConfigurationDataStore with optimistic concurrency (version
  tracking, Bad_InvalidState on mismatch)
- DefaultManagedApplicationsNodeManager that queries IConfigurationDataStore
  at startup and creates ApplicationConfigurationState instances under
  the ManagedApplications folder via external references
- IManagedApplicationsNodeManager extended with ConfigurationDataStore
  property
- Phase2AbstractionTests: 23 tests covering ConfigurationDataStore
  round-trip, version conflicts, ISecretStore integration for
  KeyCredential, AccessTokenResult, ManagedApplicationInfo, and
  interface plumbing

Co-authored-by: Copilot <[email protected]>
Update conformance matrix to reflect Phase 2 improvements:
- ConfirmUpdate and ManagedApplications now backed by
  IConfigurationDataStore + DefaultManagedApplicationsNodeManager
- Alarm lifecycle upgraded to timer-based StartAlarmMonitoring
- AuthorizationService RC methods upgraded from 'not implemented'
  to 'partial' (IAccessTokenProvider abstraction defined)
- Add Extension Points table documenting all pluggable interfaces

Co-authored-by: Copilot <[email protected]>
@codecov

codecov Bot commented May 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 50.95095% with 490 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.50%. Comparing base (239a40b) to head (14c46a5).

Files with missing lines Patch % Lines
...pc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs 57.32% 138 Missing and 25 partials ⚠️
...a.Server/Configuration/ConfigurationNodeManager.cs 8.13% 107 Missing and 6 partials ⚠️
...s.Server.Common/IManagedApplicationsNodeManager.cs 0.00% 63 Missing ⚠️
...pc.Ua.Gds.Server.Common/Diagnostics/AuditEvents.cs 42.52% 49 Missing and 1 partial ⚠️
...mon/RoleBasedUserManagement/AuthorizationHelper.cs 67.60% 20 Missing and 3 partials ⚠️
Libraries/Opc.Ua.Lds.Server/MulticastDiscovery.cs 0.00% 20 Missing ⚠️
...Ua.Gds.Client.Common/KeyCredentialServiceClient.cs 0.00% 14 Missing ⚠️
...Gds.Client.Common/ServerPushConfigurationClient.cs 0.00% 13 Missing ⚠️
...n/ApplicationsDatabase/ApplicationsDatabaseBase.cs 75.00% 9 Missing and 4 partials ⚠️
...Ua.Gds.Client.Common/AuthorizationServiceClient.cs 0.00% 8 Missing ⚠️
... and 4 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3795      +/-   ##
==========================================
- Coverage   72.00%   71.50%   -0.51%     
==========================================
  Files         679      687       +8     
  Lines      130956   132345    +1389     
  Branches    22306    22555     +249     
==========================================
+ Hits        94300    94628     +328     
- Misses      29979    31018    +1039     
- Partials     6677     6699      +22     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@marcschier marcschier changed the title GDS Part 12 Compliance — Full Implementation GDS Part 12 Full Compliance May 22, 2026
UpdateCertificateSelfSignedAsync was creating self-signed certs
without passing domainNames to CreateApplicationCertificate. The
resulting cert's SAN only contained the applicationUri — no localhost
or IP addresses. When pushed to the server (which hosts both the push
endpoint and the GDS endpoint), subsequent GDS client reconnects
failed because the server cert lacked the endpoint hostname in its
SAN. On Linux (slower timing) this manifested as a CreateSession
failure with generic Bad status in the next ordered test.

Pass m_domainNames to CreateApplicationCertificate in both the ECC
and RSA branches so the pushed cert includes proper hostnames.

Co-authored-by: Copilot <[email protected]>
Comment thread Libraries/Opc.Ua.Gds.Common/readme.md Outdated
Comment thread Libraries/Opc.Ua.Gds.Common/Design/OpcUaGdsModel.xml Outdated
marcschier and others added 2 commits May 22, 2026 11:24
- Move Part12Conformance.md to Libraries/Opc.Ua.Gds.Common/readme.md
- Keep StartsWith calls on single line in ApplicationsDatabaseBase.cs
- Keep AdministeredApplicationIds.Count == 0 (IReadOnlyList has no IsEmpty)

Co-authored-by: Copilot <[email protected]>
marcschier and others added 2 commits May 22, 2026 12:56
HasAuthenticatedSecureChannel now fails closed: throws
BadSecurityModeInsufficient when the context is not a SystemContext
with an OperationContext, instead of silently allowing the operation.

Add IGdsUserDatabase interface extending IUserDatabase with
GetAdministeredApplicationIds(userName) so that the ApplicationAdmin
privilege can be granted generically. GlobalDiscoverySampleServer
auto-detects IGdsUserDatabase at impersonation time and populates
GdsRoleBasedIdentity.AdministeredApplicationIds when present.

Co-authored-by: Copilot <[email protected]>
Break readme.md into three documents:
- readme.md: Core GDS developer guide covering client API
  (GlobalDiscoveryServerClient, ServerPushConfigurationClient),
  server-side setup (GlobalDiscoverySampleServer, factory pattern),
  provider implementation guides (IApplicationsDatabase,
  ICertificateGroup, IGdsUserDatabase, IConfigurationDataStore),
  roles/authorization, and end-to-end example
- KeyCredentialService.md: Client API, IKeyCredentialRequestStore
  provider guide with ISecretStore integration, custom implementation
  example, audit events, end-to-end example
- AuthorizationService.md: Client API, IAccessTokenProvider guide
  with OAuth2 example, AccessTokenResult, audit events, end-to-end
  example

Co-authored-by: Copilot <[email protected]>
marcschier and others added 3 commits May 22, 2026 13:47
Move GDS.md, KeyCredentialService.md, AuthorizationService.md from
Libraries/Opc.Ua.Gds.Common/ to Docs/. Add Global Discovery Server
section to Docs/README.md with links to all three guides.

Co-authored-by: Copilot <[email protected]>
Remove the two added GeneratesEvent references on UpdateApplication
and UnregisterApplication methods. These belong in the upstream
specification model, not in the local design file.

Co-authored-by: Copilot <[email protected]>
@marcschier
marcschier marked this pull request as ready for review May 22, 2026 14:07
The fail-closed check only matched SystemContext but server method
handlers receive ServerSystemContext (extends SessionSystemContext).
Use a switch expression to extract OperationContext from either
SessionSystemContext or SystemContext, failing closed when neither
matches.

Fixes BadSecurityModeInsufficient in CheckRevocationStatus and
CheckRevocationStatusUnregisteredApplications CI tests.

Co-authored-by: Copilot <[email protected]>
@marcschier
marcschier merged commit 51bcd42 into OPCFoundation:master May 23, 2026
107 of 109 checks passed
@marcschier
marcschier deleted the gdsvnext branch May 23, 2026 06:07
marcschier pushed a commit that referenced this pull request May 23, 2026
Resolved 7 merge conflicts from the DataValue class→struct migration
(#3796) and GDS Part 12 full compliance (#3795). Took master's side
for structural changes (DataValue readonly struct, new GDS methods,
new NodeState source-timestamp locals), kept our s_capsSeparator field
in MulticastDiscovery.cs alongside master's new ReverseConnect constants.

Fixes for new code violating enforced .editorconfig rules:
- CA1305/CA5394: DataValueBenchmarkPayloads.cs — use UnsecureRandom
  and CultureInfo.InvariantCulture.
- NUnit4002: DataValueTests.cs, ManualAndPollingRefreshStrategyTests.cs —
  Is.EqualTo(0) → Is.Zero, Is.EqualTo(default(DateTime)) → Is.Default.
- NUnit2046: Phase2AbstractionTests.cs — apps.Count → Has.Count.EqualTo.

0 warnings, 0 errors.

Co-authored-by: Copilot <[email protected]>
marcschier pushed a commit that referenced this pull request May 23, 2026
…only struct

Pulls in 37 upstream commits (DataValue → readonly struct via PR #3796, GDS Part 12 via PR #3795, extension-object encoding fix, pooled-encodeable activator pooling). Resolves three deleted-by-us / modified-by-them conflicts on legacy TestData/History* files (kept deleted — replaced by the new historian provider model).

Migrates all historian and capture-pipeline code to the readonly-struct DataValue API: object-initializer/setter patterns → constructor calls; DataValue? → DataValue + IsNull checks; calculator.GetProcessedValue → TryGetProcessedValue; client ValueOr* helpers take DataValue (not DataValue?); CloneValue becomes return source; (struct is copy-by-value); ApplyTimestampFilter/IndexRange/Encoding now use WithStatus/WithWrappedValue fluent mutators.

158/158 historian+fluent server tests pass; 4/4 client integration; 2/2 AOT; UA.slnx 0 errors.
marcschier pushed a commit that referenced this pull request Jul 3, 2026
…ion policy (master378) (#3936)

## Proposed changes

Backport of the #3931 fix to master378. On this branch, server-side GDS
Push `UpdateCertificate` validated the new application certificate with
a throwaway `CertificateValidator` that was never seeded with the
server's `SecurityConfiguration`. It therefore enforced hard-coded
defaults (`MinimumCertificateKeySize = 2048`,
`RejectSHA1SignedCertificates = true`) regardless of operator
configuration — e.g. a server configured for a 1024-bit minimum still
rejected a re-issued 1024-bit certificate with
`BadCertificatePolicyCheckFailed`. Regression originated in #3795
(integrity check switched from a lenient `X509Chain` check to the full
validator).

The master branch reworked this area entirely, so the fix is
re-implemented from scratch here, adapted to master378's
`X509Certificate2` types.

- **`ConfigurationNodeManager.UpdateCertificateAsync`**: replaced the
throwaway `CertificateValidator` with a lenient `X509Chain` integrity
check that verifies only chain integrity — valid signatures and a
complete issuer chain — using the caller-supplied issuers as the trust
anchor (`AllowUnknownCertificateAuthority`, revocation `NoCheck`,
downloads disabled). Policy (min key size, SHA-1, revocation) is
intentionally **not** applied here; it remains governed by the server's
`SecurityConfiguration` on its normal communication channels, so
configured values are honored in both stricter and more-lenient
directions.

Chain status handling: `NoError`/`UntrustedRoot` pass;
`NotSignatureValid`/`PartialChain`/`NotValidForUsage`/`InvalidBasicConstraints`
fail with `BadSecurityChecksFailed`; and the chain element count must
match the supplied issuer count to reject incomplete chains.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants