Skip to content

AuthenticatedProtocol

Overview

Reports protocol authentication and secure-session health independently from the connection retry lifecycle.

ConnectionBase answers "is the link up?". AuthenticatedProtocol answers "and did the device accept us?". The two occupy separate status slots on the same Thing, so an operator sees connection.retrying together with protocol.authentication.failed and knows the retry loop will never succeed without a credential change.

  • Added in: PQ Framework 2.0
  • Namespace: Pq.Adapters.Framework

When to Use

Add this function to a connection-owning Thing when the protocol has an authenticated application session on top of the transport, and the adapter can tell an authentication failure apart from plain unreachability:

  • login / credential exchange after the socket opens
  • TLS, PSK or shared-secret handshake
  • token or session-key negotiation
  • SDK connect call that reports "bad credentials" distinctly from "host unreachable"

Do not add it when the protocol has no authentication step, or when every failure surfaces as the same opaque timeout — an always-Default slot is noise.

States

State Status id Description
Default (cleared) Nothing asserted — the slot is empty
Authenticated protocol.authenticated (hidden) Protocol session is authenticated
AuthenticationFailed protocol.authentication.failed Device rejected the credentials or identity
HandshakeFailed protocol.handshake.failed Secure-session handshake failed

Authenticated is the hidden default state: SetAuthenticated(ct) publishes no status string, so a working device reports the function as cleared. Both failure states are Critical and mark the device unreliable.

The generated enum is AuthenticatedProtocolState.

Actions

Method Meaning
SetAuthenticated(ct) Session authenticated — clears the failure status
SetAuthenticationFailed(ct) Deterministic credential or identity rejection
SetHandshakeFailed(ct) Secure handshake failed and no more precise credential error is available
SetDefault(ct) Clears the slot without claiming success — use for transport-level failures

All return ValueTask<bool> (true when the status was published) and take an optional CancellationToken.

Reach them through the Thing's function container:

await panel.Functions.AuthenticatedProtocol.SetAuthenticated(ct);

Properties

None.

YAML Example

device_types:
  - type_id: Panel
    name: "Access Control Panel"
    functions:
      ConnectionBase:
      AuthenticatedProtocol:
      StatusPoll:

Code Usage

Classifying the connect path

The whole point of the function is the catch ladder: each failure class maps to a different slot value. Anything that is not provably an authentication problem must go to SetDefault — leaving a stale AuthenticationFailed on a device that simply lost its network lies to the operator.

/// <summary>Opens the authenticated session with the panel.</summary>
private async Task<bool> Connect(Panel panel, CancellationToken ct)
{
    try
    {
        await _client.Open(_endpoint, ct);
        await _client.Login(_userName, _password, ct);

        await panel.Functions.AuthenticatedProtocol.SetAuthenticated(ct);
        return true;
    }
    catch (BadCredentialsException)
    {
        // device answered and said no - retrying will not help
        await panel.Functions.AuthenticatedProtocol.SetAuthenticationFailed(ct);
        return false;
    }
    catch (SecureChannelException ex)
    {
        // TLS/PSK negotiation failed - certificate, cipher or shared secret
        Logger.LogWarning(ex, "Secure handshake failed");
        await panel.Functions.AuthenticatedProtocol.SetHandshakeFailed(ct);
        return false;
    }
    catch (Exception ex)
    {
        // transport level - not an authentication verdict
        Logger.LogWarning(ex, "Connection failed");
        await panel.Functions.AuthenticatedProtocol.SetDefault(ct);
        return false;
    }
}

Session invalidated while running

A session that expires or is revoked mid-run is an authentication failure, not a disconnect:

/// <summary>Handles a session rejection reported on the live stream.</summary>
private async Task HandleSessionRejected(Panel panel, CancellationToken ct)
{
    await panel.Functions.AuthenticatedProtocol.SetAuthenticationFailed(ct);
    await panel.DisconnectedAsync();
}

The framework then drives the normal retry loop through ConnectionBase, and the protocol slot keeps explaining why each attempt fails until a successful login calls SetAuthenticated.

Status polling

AuthenticatedProtocol state comes from the connect and session path, not from a device register. When the Thing also has StatusPoll, exclude the state explicitly so the poll-coverage check stays honest:

[PollExcludes(typeof(AuthenticatedProtocolState), "Protocol authentication is reported by the connection handshake path.")]
public static Task PollStatus(Panel panel, Protocol protocol, CancellationToken ct) =>
    protocol.PollAllStates(panel, ct);

Notes

  • ConnectionBase keeps owning connection.online / connection.offline / connection.retrying. This function never replaces them — it annotates them.
  • Set the state only from an explicit vendor error code, SDK exception type, or protocol response. A guess is worse than an empty slot.
  • Never map a network timeout, refused port, or DNS failure to AuthenticationFailed or HandshakeFailed — use SetDefault.
  • Prefer AuthenticationFailed when the device distinguishes bad credentials; fall back to HandshakeFailed when the failure is only known to be inside the secure-session setup.
  • Both failure states drive the device to unreliable in reliability accounting, so they must clear as soon as a session succeeds.

See Also

  • Rules: RULE-058 - an authentication verdict must come from the device, never from a transport failure
  • Functions: ConnectionBase - connection lifecycle this function annotates
  • Functions: StatusPoll - polling declared states
  • Diagnostics: PQC251 - poll coverage and declaring [PollExcludes]
  • Patterns: Connection Management - implementing device connections
  • Concepts: Status - status slots, hidden states and aggregation