Files
suitelinkclient/docs/plans/2026-03-16-suitelink-client-design.md
2026-03-16 14:43:31 -04:00

7.5 KiB

SuiteLink Tag Client Design

Goal

Build a cross-platform .NET 10 C# client that communicates with AVEVA SuiteLink from macOS, Linux, and Windows for tag operations only.

The v1 scope is limited to:

  • Connect to a SuiteLink endpoint
  • Subscribe to tags
  • Receive tag updates
  • Write tag values
  • Unsubscribe cleanly

The v1 scope explicitly excludes:

  • AlarmMgr
  • Alarm and event handling
  • Secure SuiteLink V3 support
  • Automatic reconnect and subscription rebuild

Constraints

  • The protocol is proprietary and the current design is based on reverse-engineered public evidence plus AVEVA product documentation.
  • The initial target is non-encrypted SuiteLink V2 behavior, or servers configured for mixed mode that still permit legacy SuiteLink traffic.
  • The first implementation only supports primitive value types:
    • bool
    • int32
    • float32
    • string
  • The design should remain extensible for later support of double, int64, and DateTime if packet captures confirm their wire representation.

Protocol Target

The target protocol surface is the normal SuiteLink tag exchange path, not AlarmMgr.

Observed normal message structure:

  • uint16 little-endian remaining_length
  • uint16 little-endian message_type
  • payload
  • trailing byte 0xA5

Observed message types to support:

  • CONNECT
  • ADVISE
  • ADVISE ACK
  • UPDATE
  • UNADVISE
  • UNADVISE ACK
  • POKE
  • POKE ACK
  • TIME
  • Ping/pong keepalive messages

Observed normal wire value types:

  • binary
  • integer
  • real
  • message

Architecture

The client is split into three layers.

Transport

Responsible for:

  • Opening and closing TCP connections
  • Reading complete SuiteLink frames
  • Writing complete SuiteLink frames
  • Cancellation and socket lifetime management

Protocol

Responsible for:

  • Encoding the startup handshake
  • Encoding CONNECT
  • Encoding ADVISE, UNADVISE, and POKE
  • Decoding handshake acknowledgements
  • Decoding ADVISE ACK, UPDATE, and keepalive traffic
  • Converting between wire values and typed client values

Client API

Responsible for:

  • Exposing a minimal public API for connect, subscribe, read, write, and disconnect
  • Hiding protocol details such as server-assigned tag ids
  • Dispatching updates to user callbacks or future stream abstractions

Session Model

The session uses one persistent TCP connection to one SuiteLink endpoint and one configured application/topic pair.

State model:

  • Disconnected
  • TcpConnected
  • HandshakeComplete
  • SessionConnected
  • Faulted

Startup flow:

  1. Open TCP connection.
  2. Send SuiteLink handshake for the normal tag protocol.
  3. Validate handshake acknowledgement.
  4. Send CONNECT with application, topic, and client identity fields.
  5. Transition to connected session state.
  6. Send ADVISE for one or more items.
  7. Capture tag_id mappings from server responses.
  8. Receive UPDATE frames and dispatch typed values to subscribers.
  9. Send POKE for writes.
  10. Send UNADVISE when a subscription is disposed.

Read behavior in v1 is implemented as a temporary subscription:

  1. Send ADVISE for the requested item.
  2. Wait for the first matching UPDATE.
  3. Return the decoded value.
  4. Send UNADVISE.

This is preferred over inventing a direct read request that has not yet been proven by packet captures.

Public API

public sealed class SuiteLinkClient : IAsyncDisposable
{
    Task ConnectAsync(SuiteLinkConnectionOptions options, CancellationToken ct = default);
    Task DisconnectAsync(CancellationToken ct = default);

    Task<SubscriptionHandle> SubscribeAsync(
        string itemName,
        Action<SuiteLinkTagUpdate> onUpdate,
        CancellationToken ct = default);

    Task<SuiteLinkTagUpdate> ReadAsync(
        string itemName,
        TimeSpan timeout,
        CancellationToken ct = default);

    Task WriteAsync(
        string itemName,
        SuiteLinkValue value,
        CancellationToken ct = default);
}

Supporting models:

  • SuiteLinkConnectionOptions
    • Host
    • Port default 5413
    • Application
    • Topic
    • ClientName
    • ClientNode
    • UserName
    • ServerNode
  • SuiteLinkValue
    • typed union for bool, int, float, and string
  • SuiteLinkTagUpdate
    • ItemName
    • TagId
    • Value
    • Quality
    • ElapsedMilliseconds
    • ReceivedAtUtc
  • SubscriptionHandle
    • caller-facing subscription lifetime object
    • disposes via UNADVISE

Internal Components

SuiteLinkFrameReader

Responsibilities:

  • Read complete normal SuiteLink frames from a NetworkStream
  • Parse the 2-byte little-endian remaining length
  • Validate trailing 0xA5
  • Return frame payload slices to the codec

SuiteLinkFrameWriter

Responsibilities:

  • Build frames in memory
  • Write little-endian lengths and message types
  • Encode UTF-16LE strings where required
  • Append trailing 0xA5

SuiteLinkMessageCodec

Responsibilities:

  • Encode handshake and normal session messages
  • Decode incoming acknowledgements and updates
  • Map wire value types to SuiteLinkValue

Expected methods:

  • EncodeHandshake
  • EncodeConnect
  • EncodeAdvise
  • EncodeUnadvise
  • EncodePoke
  • DecodeHandshakeAck
  • DecodeAdviseAck
  • DecodeUpdate
  • DecodeKeepAlive

SuiteLinkSession

Responsibilities:

  • Own the send and receive loops
  • Maintain session state
  • Track itemName <-> tagId mappings
  • Track active subscriptions
  • Route decoded updates to the correct subscriber callbacks

Type Strategy

The first release supports only:

  • bool
  • int32
  • float32
  • string

The public type wrapper must be extensible so later additions do not require replacing the whole API. The intended future-compatible additions are:

  • double
  • int64
  • DateTime

The protocol layer should fail fast when an unsupported wire type or unsupported outgoing write type is encountered.

Error Handling

The v1 client should be explicit and conservative.

  • Any malformed frame transitions the session to Faulted
  • Any unexpected message type during startup fails the connection attempt
  • Write attempts for unsupported types fail immediately
  • Reconnect is not automatic in v1
  • Subscription rebuild after reconnect is deferred to a later version

Validation Strategy

Testing is divided into three levels.

Unit Tests

Validate:

  • frame length handling
  • trailing marker validation
  • UTF-16LE string encoding/decoding
  • primitive value encoding/decoding

Golden Packet Tests

Use known byte sequences and captures to verify:

  • handshake
  • CONNECT
  • ADVISE
  • ADVISE ACK
  • UPDATE
  • POKE
  • UNADVISE

Integration Tests

Run against a real AVEVA/OI server configured to allow legacy or mixed-mode SuiteLink traffic.

Success criteria:

  • connect successfully from macOS or Linux
  • subscribe to one boolean tag
  • subscribe to one integer tag
  • subscribe to one real tag
  • subscribe to one message tag
  • receive live updates for each
  • write to each supported tag type and verify the result
  • disconnect cleanly

Non-Goals

The following are intentionally deferred:

  • AlarmMgr support
  • Secure SuiteLink V3 support
  • automatic reconnect
  • batched subscription optimization
  • broad type support beyond the four proven primitive classes
  • production hardening for all undocumented server variants

Create a detailed implementation plan that:

  • establishes the project structure
  • defines the test-first workflow
  • identifies capture-driven fixtures needed for codec tests
  • breaks the implementation into transport, codec, session, and API slices