OPC UA Part 4 — Client/Server (UACP + SecureChannel) — Spec Coverage
Source: internal/standards/cache/opcfoundation/opcua-part4-services-1.05.07.pdf
(Services). Cross-spec:
opcua-part6-mappings-1.05.07.pdf (UACP §7.1, OPC UA Secure Conversation §6.7,
binary codec §5.2),
opcua-part7-profiles-1.05.02.pdf (transport profiles, SecurityPolicies)
(all in the same cache; not tracked in the repo for IP/copyright reasons).
Public spec: OPC UA online reference https://reference.opcfoundation.org/ (Part 4 Services, Part 6 Mappings, Part 7 Profiles).
Context: A native pure-Rust no_std + alloc OPC-UA Client/Server stack —
the request/response counterpart to the UADP PubSub stack
(zerodds-opcua-pubsub). Same approach as ZeroDDS’s full CORBA/MQTT/AMQP
stacks: a real wire stack, not just a gateway bridge. The Part 6 binary codec
is reused from zerodds-opcua-pubsub. forbid(unsafe_code).
Implementation:
crates/opcua-uacp/· docs.rs — UACP (Hello/Ack/Error/ReverseHello), OPC UA Secure Conversation (OPN/MSG/CLO chunks, security headers, SequenceHeader, ChannelSecurityToken, message chunking acrossC/F/Awith reassembly) and the secured SecurityPolicies (featurecrypto).crates/opcua-server/· docs.rs — the Discovery, SecureChannel, Session, Attribute, View, Method, MonitoredItem and Subscription service sets, an in-memory AddressSpace with the Part 3 attribute model and a reference graph, theServerstate machine with sessions/user store/SecureChannel list, theClientand theopc.tcpTCP transport.
SecurityMode None and the secured SecurityPolicies (RSA/AES crypto,
Basic256Sha256/Aes128_Sha256_RsaOaep/Aes256_Sha256_RsaPss) are implemented
end to end in both modes (Sign, SignAndEncrypt). Open items: none.
Part 6 — UACP, SecureChannel and binary codec
§7.1 OPC UA Connection Protocol (UACP)
Spec: Part 6 §7.1, p. 80 — message structure (8-byte header
MessageType + ChunkType + MessageSize), Hello/Acknowledge/Error and
ReverseHello (§7.1.3 Establishing a connection, p. 83).
Repo: crates/opcua-uacp/src/connection.rs (MessageType, ChunkType,
MessageHeader, HelloMessage, AcknowledgeMessage, ErrorMessage,
ReverseHelloMessage).
Tests: crates/opcua-uacp/src/connection.rs::tests::header_round_trips_and_sizes_match,
acknowledge_round_trips, error_round_trips, reverse_hello_round_trips,
unknown_message_type_rejected, oversized_string_rejected.
Status: done
§6.7 OPC UA Secure Conversation — chunk framing and message chunking
Spec: Part 6 §6.7, p. 62 — SecureChannel messages as chunks (OPN/MSG/CLO),
AsymmetricAlgorithmSecurityHeader (OPN) /
SymmetricAlgorithmSecurityHeader (MSG/CLO), SequenceHeader
(§6.7.2 MessageChunk structure). §6.7.1: “OPC UA Secure Conversation will
break OPC UA Messages into several pieces (called ‘MessageChunks’) that are
smaller than the buffer size allowed by the TransportProtocol”; §6.7.2.2
Table 57 IsFinal = C (intermediate) / F (final) / A (abort); §6.7.3:
“The sender indicates that the MessageChunk contains an error by setting the
IsFinal flag to ‘A’” and “the receiver shall ignore the Message but shall not
close the SecureChannel”.
Repo: crates/opcua-uacp/src/securechannel.rs
(SecureChannel::open_chunk / message_chunks / message_chunk /
close_chunk / abort_chunk, set_send_buffer_size +
MIN_SEND_BUFFER_SIZE (§6.7.1: at least 8192 bytes with RSA),
open_incoming reassembling across C chunks into ChunkOutcome
(Incomplete/Complete/Abort), the MAX_REASSEMBLED_MESSAGE cap,
split_messages, parse_chunk, ParsedChunk, SECURITY_POLICY_NONE);
secured chunk types via
crates/opcua-uacp/src/crypto.rs::build_symmetric_chunk_typed +
OpenedChunk::chunk_type. The buffer size negotiated in Hello/Acknowledge is
applied in crates/opcua-server/src/server.rs (Server::process) and
crates/opcua-server/src/client.rs (Client::open_channel).
Tests: crates/opcua-uacp/src/securechannel.rs::tests::message_chunk_round_trip,
open_secure_channel_round_trip_none, chunked_message_round_trips_across_c_and_f,
abort_chunk_reports_the_error_and_keeps_the_channel,
split_messages_splits_by_message_size;
end-to-end over both transports:
crates/opcua-server/src/client.rs::tests::e2e_loopback_message_larger_than_the_buffer_is_chunked,
e2e_tcp_message_larger_than_the_buffer_is_chunked.
Status: done
§6.7 SecureChannel crypto — secured SecurityPolicies
Spec: Part 6 §6.7.2 (chunk security layout: SequenceHeader‖Body‖Padding‖
Signature, the signature covering MessageHeader…Padding, then encrypted),
§6.7.6 Deriving keys (p. 70, P-SHA256); Part 7 SecurityPolicies
Basic256Sha256, Aes128_Sha256_RsaOaep, Aes256_Sha256_RsaPss.
Repo (crypto core): crates/opcua-uacp/src/crypto.rs (feature crypto):
SecurityPolicy (URI/algorithm parameters), p_sha256 + derive_keys
(§6.7.6: signing/encrypting/IV), symmetric build_symmetric_chunk/
open_symmetric_chunk (AES-128/256-CBC + HMAC-SHA256, padding, Sign and
SignAndEncrypt), asymmetric build_asymmetric_chunk/open_asymmetric_chunk
(RSA-OAEP-SHA1/SHA256 + RSA-PKCS#1-v1.5/PSS over SHA-256, block-wise OPN crypto
with padding/ExtraPaddingByte + certificate thumbprint via sha1_thumbprint).
Repo (wiring): crates/opcua-uacp/src/securechannel.rs
(SecuritySession, SecureChannel::install_security/open_incoming,
secured message_chunk/close_chunk), crates/opcua-server/src/server.rs
(ServerSecurity, Server::set_security, open_secure_channel_secured:
open the OPN → nonce → key derivation → secured OPN response + session install,
for ISSUE as well as RENEW),
crates/opcua-server/src/client.rs (ClientSecurity, Client::set_security,
open_secure_channel_secured). The trust store is the peer certificates /
public keys handed in; the RNG is caller-supplied
(Box<dyn CryptoRngCore + Send>, OS RNG under std).
Tests: crates/opcua-uacp/src/crypto.rs::tests::p_sha256_is_deterministic_and_sized,
derive_keys_lengths_and_mirror, symmetric_sign_and_encrypt_round_trip
(all 3 policies), symmetric_sign_only_round_trip, symmetric_tamper_is_rejected,
asymmetric_round_trip_oaep_pkcs15, asymmetric_round_trip_oaep_sha256_pss,
asymmetric_wrong_signer_is_rejected, full_secured_handshake_flow;
a secured end-to-end per mode:
crates/opcua-server/src/client.rs::tests::e2e_secured_loopback_sign_and_encrypt
(real RSA keys + OS RNG: encrypted+signed handshake → Read/Call/Write over
Basic256Sha256/SignAndEncrypt) and
e2e_secured_loopback_sign_only_and_renew (Aes256_Sha256_RsaPss/Sign:
handshake → Read, an EndpointDescription carrying
MessageSecurityMode::Sign and the policy URI, then RENEW over the secured
channel).
Status: done
§5.2 OPC UA Binary — codec reuse
Spec: Part 6 §5.2, p. 18 — binary encoding (built-in types, NodeId, Variant, DataValue, ExtensionObject, arrays).
Repo: reused from crates/opcua-pubsub/src/binary/ via
UaReader/UaWriter/UaEncode/UaDecode; NodeId/Variant/DataValue/
ExtensionObject from zerodds-opcua-gateway.
Tests: crates/opcua-pubsub/src/binary/builtin.rs::tests (20 tests), among them
nodeid_two_byte_compact_form, guid_encodes_data1_little_endian,
variant_scalar_int32, variant_multidim_array, data_value_full;
crates/opcua-pubsub/src/binary/io.rs::tests::primitive_roundtrip_is_little_endian,
::read_past_end_reports_eof, ::floats_roundtrip;
crates/opcua-pubsub/src/binary/mod.rs::tests::read_array_rejects_oversized_length,
::read_string_array_roundtrip_within_bound.
Status: done
§7.2 OPC UA TCP — opc.tcp transport (end-to-end)
Spec: Part 6 §7.2, p. 86 — the UA-TCP mapping; transport profile
uatcp-uasc-uabinary (Part 7).
Repo: crates/opcua-server/src/client.rs
(TcpTransport, serve_connection, the Transport trait,
LoopbackTransport), crates/opcua-server/src/server.rs (Server::process).
Tests: crates/opcua-server/src/client.rs::tests::e2e_tcp_connect_read_call
(a real opc.tcp socket: connect/read/call over TcpListener + thread),
e2e_loopback_connect_read_call, e2e_loopback_write_then_read.
Status: done
Part 4 — Service Sets
§5.6.2 OpenSecureChannel
Spec: Part 4 §5.6.2, p. 21 f. — OpenSecureChannelRequest/Response,
SecurityTokenRequestType (Table 11: “ISSUE creates a new SecurityToken for a
new SecureChannel. RENEW creates a new SecurityToken for an existing
SecureChannel”), secureChannelId (“shall be null when creating a new
SecureChannel”), ChannelSecurityToken (RevisedLifetime). §5.6.2.1: “Clients
should request a new SecurityToken after 75 % of its lifetime has elapsed” and
“A Server application should limit the number of SecureChannels … the Server
shall close the oldest unused SecureChannel”.
Repo: crates/opcua-uacp/src/securechannel.rs
(OpenSecureChannelRequest, OpenSecureChannelResponse,
SecurityTokenRequestType, ChannelSecurityToken, SecureChannel::adopt_token
/ reset_sequences), crates/opcua-server/src/server.rs
(Server::open_secure_channel and open_secure_channel_secured with an ISSUE
and a RENEW branch, Server::channels as the list of open SecureChannels with
MAX_SECURE_CHANNELS and eviction of the oldest, chunk_channel_id selecting
the channel per chunk from the header’s SecureChannelId),
crates/opcua-server/src/client.rs (Client::renew_secure_channel,
open_secure_channel_plain/open_secure_channel_secured taking a
SecurityTokenRequestType — the RENEW OPN carries its own SecureChannelId,
the ISSUE OPN the null id).
Tests: crates/opcua-uacp/src/securechannel.rs::tests::open_response_round_trip,
crates/opcua-server/src/client.rs::tests::e2e_loopback_connect_read_call
(runs through OPN), e2e_loopback_secure_channel_renew_keeps_the_channel_working
(RENEW in mode None: the session and a Read survive the token change),
e2e_secured_loopback_sign_only_and_renew (RENEW over the secured channel),
e2e_loopback_transfer_subscriptions_between_sessions (two clients, two
SecureChannels open at once on the same server).
Status: done
§5.7.2 CreateSession
Spec: Part 4 §5.7.2, p. 24 — CreateSessionRequest/Response
(SessionId, AuthenticationToken, RevisedSessionTimeout, ServerEndpoints,
ServerSignature).
Repo: crates/opcua-server/src/services.rs
(CreateSessionRequest, CreateSessionResponse),
crates/opcua-server/src/server.rs (Server::create_session: creates the
Session, draws the 32-byte serverNonce (session_nonce, from the
configured CSPRNG under crypto) and returns the serverCertificate plus an
EndpointDescription carrying the UserTokenPolicys of the UserStore).
Tests: crates/opcua-server/src/client.rs::tests::e2e_loopback_connect_read_call,
e2e_tcp_connect_read_call, e2e_loopback_user_name_identity_token.
Status: done
§5.7.3 ActivateSession — UserIdentityTokens
Spec: Part 4 §5.7.3, p. 29 ff. — ActivateSessionRequest/Response
(ClientSignature, LocaleIds, UserIdentityToken, UserTokenSignature,
ServerNonce). §5.7.3.1: “Once used, a serverNonce cannot be used again. For
that reason, the Server returns a new serverNonce each time the
ActivateSession Service is called” and “Clients can change the identity of a
user associated with a Session by calling the ActivateSession Service”.
§5.7.3.2: “Null or empty user token shall always be interpreted as anonymous”,
and the serverNonce “shall have a length between 32 and 128 bytes inclusive.
The Client shall check the length”. §7.40 Table 181 — the four
UserIdentityTokens (AnonymousIdentityToken §7.40.3, UserNameIdentityToken
§7.40.4, X509IdentityToken §7.40.5, IssuedIdentityToken §7.40.6); §7.41
UserTokenPolicy, §7.42 UserTokenType. Result codes §5.7.3.3:
Bad_IdentityTokenInvalid, Bad_IdentityTokenRejected,
Bad_UserAccessDenied, Bad_UserSignatureInvalid.
Repo: crates/opcua-server/src/identity.rs (UserIdentityToken with all
four Table 181 types and their _Encoding_DefaultBinary NodeIds in
token_ids, UserStore with add_user/add_certificate/add_issued_token/
set_allow_anonymous and validate, user_token_policy for the §7.41 policy
in the EndpointDescription, ClientUserId),
crates/opcua-server/src/services.rs (ActivateSessionRequest,
ActivateSessionResponse), crates/opcua-server/src/server.rs
(Server::set_user_store, the Session list with activated/user/nonce,
activate_session validating the token and drawing a fresh nonce per call,
require_activated_session rejecting every session service before activation
with Bad_SessionNotActivated), crates/opcua-server/src/client.rs
(Client::set_identity, change_user, user_token_signature — the §6.1.8
signature over serverCertificate‖serverNonce via
crates/opcua-uacp/src/crypto.rs::sign_with_policy/signature_data).
Tests: crates/opcua-server/src/identity.rs::tests::all_four_table_181_tokens_round_trip_through_the_extension_object,
null_token_is_anonymous, user_token_policy_round_trips,
store_accepts_each_token_type_and_rejects_bad_credentials,
anonymous_is_rejected_when_the_server_does_not_allow_it;
crates/opcua-server/src/services.rs::tests::session_lifecycle_round_trips;
end-to-end crates/opcua-server/src/client.rs::tests::e2e_loopback_user_name_identity_token
(the published USERNAME policy, anonymous refused, a wrong password giving
Bad_UserAccessDenied, the right one activating) and
e2e_loopback_x509_and_issued_identity_tokens_and_user_change
(X.509 and IssuedToken activation, an identity change on the running session,
a forged token and an untrusted certificate refused),
e2e_loopback_session_must_be_activated_and_close_deletes_subscriptions.
Status: done
§5.7.4 CloseSession
Spec: Part 4 §5.7.4, p. 31 — CloseSessionRequest (DeleteSubscriptions)
/Response.
Repo: crates/opcua-server/src/services.rs
(CloseSessionRequest, CloseSessionResponse).
Tests: crates/opcua-server/src/services.rs::tests::session_lifecycle_round_trips.
Status: done
§5.11.2 Read — every attribute of the AddressSpace
Spec: Part 4 §5.11.2, p. 45 ff. — “This Service is used to read one or more
Attributes of one or more Nodes”; ReadRequest (MaxAge, TimestampsToReturn,
ReadValueId[]) / ReadResponse (DataValue[]). §5.11.2.2: “Negative values
are invalid for maxAge”; indexRange per §7.27 / Annex A.3; §7.39
TimestampsToReturn. Result codes §5.11.2.3/.4: Bad_MaxAgeInvalid,
Bad_TimestampsToReturnInvalid, Bad_NothingToDo, Bad_NodeIdUnknown,
Bad_AttributeIdInvalid, Bad_IndexRangeInvalid, Bad_IndexRangeNoData,
Bad_NotReadable, Bad_DataEncodingInvalid. The attribute ids are listed in
Part 6 Annex A.1 Table A.1 (1 NodeId … 27 AccessLevelEx), the NodeClass
each belongs to in Part 3 §5.
Repo: crates/opcua-server/src/attributes.rs (attribute_ids = Table A.1,
ClassAttributes holding the Part 3 attribute set per NodeClass, the
access_level bits per Part 3 §8.57, NumericRange (parser + apply) per
§7.27/Annex A.3, TimestampsToReturn),
crates/opcua-server/src/address_space.rs (NodeMeta with
Description/WriteMask/UserWriteMask/ClassAttributes,
AddressSpace::read_attribute returning every attribute of the NodeClass and
Bad_AttributeIdInvalid for every other), crates/opcua-server/src/status.rs
(the StatusCodes), crates/opcua-server/src/services.rs
(ReadRequest, ReadResponse, ReadValueId),
crates/opcua-server/src/server.rs (Server::read validating maxAge and
TimestampsToReturn, read_one applying indexRange and dataEncoding),
crates/opcua-server/src/client.rs (Client::read_attributes,
read_attribute, read_values, read_value_id).
Tests: crates/opcua-server/src/attributes.rs::tests::numeric_range_parses_the_annex_a3_grammar,
numeric_range_rejects_the_forms_the_spec_calls_invalid,
numeric_range_apply_selects_and_clamps,
timestamps_to_return_strips_what_was_not_asked_for;
crates/opcua-server/src/services.rs::tests::read_request_round_trips,
read_response_round_trips;
end-to-end crates/opcua-server/src/client.rs::tests::e2e_loopback_reads_every_attribute_of_a_variable
(16 attributes of one Variable, each Good with its value checked;
Executable and an id outside Table A.1 as Bad_AttributeIdInvalid),
e2e_loopback_index_range_read_and_write,
e2e_loopback_read_honours_timestamps_to_return_and_max_age,
read_unknown_node_yields_bad_status.
Status: done
§5.11.4 Write — every writeable attribute
Spec: Part 4 §5.11.4, p. 50 ff. — “This Service is used to write values to
one or more Attributes of one or more Nodes”; attributeId “shall be a
valid Attribute id … The IntegerIds for the Attributes are defined in
OPC 10000-6”; indexRange per §7.27; “If the SourceTimestamp or the
ServerTimestamp is specified, the Server shall use these values. The Server
returns a Bad_WriteNotSupported error if it does not support writing of
timestamps.” Result codes §5.11.4.4: Bad_NodeIdUnknown,
Bad_AttributeIdInvalid, Bad_IndexRangeInvalid, Bad_IndexRangeNoData,
Bad_WriteNotSupported, Bad_NotWritable, Bad_TypeMismatch. Which attribute
is writeable is defined in Part 3 §8.60 Table 44 (AttributeWriteMask,
bits 0–25) — with the explicit special case that the Value attribute of a
Variable is not in the mask but governed by AccessLevel/UserAccessLevel
(Part 3 §8.57).
Repo: crates/opcua-server/src/attributes.rs (the write_mask bits =
Table 44, write_mask_bit mapping attribute id to bit and deliberately
returning None for Value), crates/opcua-server/src/address_space.rs
(AddressSpace::write_attribute: WriteMask ∧ UserWriteMask, then
write_value_attribute with CurrentWrite/TimestampWrite/StatusWrite
or ValueForVariableType, and write_class_attribute per NodeClass;
Bad_TypeMismatch on a wrong builtin type, Bad_WriteNotSupported for
NodeId/NodeClass), crates/opcua-server/src/services.rs
(WriteRequest, WriteResponse, WriteValue),
crates/opcua-server/src/server.rs (Server::write_one resolving
indexRange against the current value), crates/opcua-server/src/client.rs
(Client::write_attributes, write_values).
Tests: crates/opcua-server/src/attributes.rs::tests::write_mask_bit_matches_table_44,
numeric_range_replace_writes_in_place;
crates/opcua-server/src/services.rs::tests::write_round_trips;
end-to-end crates/opcua-server/src/client.rs::tests::e2e_loopback_write_respects_the_write_mask_and_access_level
(a set mask bit writes, a cleared one gives Bad_NotWritable, a missing
CurrentWrite gives Bad_NotWritable, a wrong type gives Bad_TypeMismatch,
and the refused write leaves the value unchanged),
e2e_loopback_index_range_read_and_write, e2e_loopback_write_then_read.
Status: done
§5.12.2 Call
Spec: Part 4 §5.12.2, p. 54 — CallRequest (CallMethodRequest[]) /
CallResponse (CallMethodResult[]).
Repo: crates/opcua-server/src/services.rs
(CallMethodRequest, CallMethodResult, CallRequest, CallResponse),
crates/opcua-server/src/address_space.rs (method handler registry),
crates/opcua-server/src/client.rs (Client::call_method).
Tests: crates/opcua-server/src/services.rs::tests::call_round_trips,
crates/opcua-server/src/address_space.rs::tests::method_call_dispatch,
crates/opcua-server/src/client.rs::tests::e2e_loopback_connect_read_call,
e2e_tcp_connect_read_call.
Status: done
§5.9.2 / §5.9.3 Browse + BrowseNext (View Service Set)
Spec: Part 4 §5.9.2 Browse (p. 38 ff.), §5.9.3 BrowseNext (p. 40 ff.),
§7.29 ReferenceDescription (p. 148), §7.6 BrowseResult (p. 118), §7.9
ContinuationPoint (p. 129), §7.44 ViewDescription (p. 169); ExpandedNodeId
Part 6 §5.2.2.10. §5.9.2.2 requestedMaxReferencesPerNode: “Indicates the
maximum number of references to return for each starting Node … The value 0
indicates that the Client is imposing no limitation.” §5.9.3.1: “This Service
is used to request the next set of Browse or BrowseNext response information
that is too large to be sent in a single response.” §5.9.3.2
releaseContinuationPoints: “TRUE — passed continuationPoints shall be reset
to free resources in the Server. The continuation points are released and the
results and diagnosticInfos arrays are empty.” Result codes §5.9.2.4/§5.9.3.4:
Bad_NodeIdUnknown, Bad_BrowseDirectionInvalid, Bad_ReferenceTypeIdInvalid,
Bad_NoContinuationPoints, Bad_ContinuationPointInvalid, Bad_NothingToDo.
Repo: crates/opcua-server/src/address_space.rs (node/reference model:
NodeMeta, ReferenceRecord, NodeClass, the ns0 reference_types
constants, add_node/add_reference/node_meta/browse with direction +
reference-subtype filter (includeSubtypes) + NodeClass mask),
crates/opcua-server/src/services.rs (ViewDescription, BrowseDescription,
ReferenceDescription, BrowseResult, BrowseRequest/BrowseResponse,
BrowseNextRequest/BrowseNextResponse (NodeIds 533/536), the ExpandedNodeId
wire form); crates/opcua-server/src/server.rs (browse_one: reference filter
+ resultMask projection via project_reference, Bad_NodeIdUnknown for
unknown nodes, Bad_BrowseDirectionInvalid and Bad_ReferenceTypeIdInvalid
via is_known_reference_type, capping at requestedMaxReferencesPerNode with
allocate_continuation_point; browse_next_one continuing or releasing; the
ContinuationPoint list bounded by MAX_CONTINUATION_POINTS →
Bad_NoContinuationPoints), crates/opcua-server/src/client.rs
(Client::browse, browse_limited, browse_next, browse_all).
Tests: crates/opcua-server/src/address_space.rs::tests::browse_forward_and_inverse,
browse_subtype_filter_excludes_non_hierarchical, browse_node_class_mask_filters_targets,
crates/opcua-server/src/services.rs::tests::browse_round_trips,
crates/opcua-server/src/client.rs::tests::e2e_loopback_browse
(Objects→Boiler→Temperature over Organizes/HasComponent) and
e2e_loopback_browse_next_walks_continuation_points (five references,
requestedMaxReferencesPerNode = 2 → a first batch plus a continuation point,
BrowseNext returning the remaining three in order, release freeing the point
so a second use of it is Bad_ContinuationPointInvalid, and browse_all
walking the whole chain at batch size 1).
Status: done
§5.5.4 / §5.5.2 Discovery — GetEndpoints + FindServers
Spec: Part 4 §5.5.4 GetEndpoints (p. 14), §5.5.2 FindServers (p. 12) — session-less discovery services (SecureChannel only, no session required).
Repo: crates/opcua-server/src/services.rs
(GetEndpointsRequest/GetEndpointsResponse (NodeIds 426/429),
FindServersRequest/FindServersResponse (NodeIds 422/425)),
crates/opcua-server/src/server.rs (dispatch arms: GetEndpoints returns the
server’s EndpointDescription — Server::endpoint with the UserTokenPolicys
from the UserStore and, under crypto, the MessageSecurityMode and policy
URI actually configured (endpoint_security) —, FindServers its
ApplicationDescription), crates/opcua-server/src/client.rs
(Client::open_channel — Hello+OPN without a session — plus
Client::get_endpoints/find_servers).
Tests: crates/opcua-server/src/services.rs::tests::discovery_requests_round_trip,
crates/opcua-server/src/client.rs::tests::e2e_loopback_discovery
(session-less channel → GetEndpoints returns an endpoint with SecurityPolicy
None, FindServers returns the server);
e2e_secured_loopback_sign_only_and_renew checks the same endpoint under
Aes256_Sha256_RsaPss/Sign, e2e_loopback_user_name_identity_token the
published USERNAME UserTokenPolicy.
Status: done
§5.13 / §5.14 Subscription + MonitoredItem Service Sets
Spec: Part 4 §5.13.2 CreateMonitoredItems (p. 63), §5.13.3 ModifyMonitoredItems (p. 65), §5.13.4 SetMonitoringMode (p. 67), §5.13.5 SetTriggering (p. 68), §5.13.6 DeleteMonitoredItems (p. 69), §5.14.2 CreateSubscription (p. 76), §5.14.3 ModifySubscription (p. 78), §5.14.4 SetPublishingMode (p. 79), §5.14.5 Publish (p. 81), §5.14.6 Republish (p. 83), §5.14.7 TransferSubscriptions (p. 83), §5.14.8 DeleteSubscriptions (p. 85); §7.21 MonitoringParameters, §7.23 MonitoringMode, §7.25.2 DataChangeNotification (p. 145), §7.25.4 StatusChangeNotification (p. 146), §7.26 NotificationMessage (p. 147), §7.39 TimestampsToReturn.
The sampling rules this server is measured against are in §5.14.1: the subscription “attempts to send a NotificationMessage” per cycle (b), a cycle without notifications produces a keep-alive message (f), and the session keeps a “retransmission queue of sent NotificationMessages … until they are acknowledged” (i). §5.13.4.1: “Setting the mode to DISABLED causes all queued Notifications to be deleted.” §5.13.6.1: “When a MonitoredItem is deleted, its triggered item links are also deleted.” §5.14.3.2: “If the requested value is 0 or negative, the Server shall revise with the fastest supported publishing interval”, “The lifetime count shall be a minimum of three times the keep-alive count”. §5.14.7.1: “the Server shall issue a StatusChangeNotification notificationMessage with the status code Good_SubscriptionTransferred to the old Session”.
Repo: crates/opcua-server/src/services.rs — the twelve services of both
service sets as wire types: CreateSubscriptionRequest/Response,
ModifySubscriptionRequest/Response (793/796), SetPublishingModeRequest/
Response, PublishRequest/Response, RepublishRequest/Response
(832/835), TransferSubscriptionsRequest/Response + TransferResult
(841/844/838), DeleteSubscriptionsRequest/Response,
CreateMonitoredItemsRequest/Response, ModifyMonitoredItemsRequest/
Response + MonitoredItemModifyRequest/MonitoredItemModifyResult
(763/766/757/760), SetMonitoringModeRequest/Response (769/772),
SetTriggeringRequest/Response (775/778), DeleteMonitoredItemsRequest/
Response (781/784), plus MonitoringParameters,
SubscriptionAcknowledgement, MonitoredItemNotification,
DataChangeNotification (811) and StatusChangeNotification (820).
crates/opcua-server/src/server.rs — the subscription state: Subscription
with its revised parameters (revise_publishing_interval/revise_keep_alive/
revise_lifetime per §5.14.3.2), the retransmission_queue of depth
RETRANSMISSION_QUEUE_DEPTH, MonitoredItem with MonitoringMode,
queue/queue_size/discard_oldest (§5.13.1.5, trim_queue),
index_range, timestamps_to_return and triggered_items (§5.13.5);
Server::publish processes the acknowledgements first, reports the §7.25.4
StatusChangeNotification to a session whose subscription was taken over,
then samples via sample_subscription/sample_item and delivers the queues of
the reporting items together with the sampling items they trigger, bounded by
max_notifications_per_publish (more_notifications); with nothing to report
a keep-alive message goes out, and with no subscription at all the service
result Bad_NoSubscription. crates/opcua-server/src/attributes.rs
(MonitoringMode, TimestampsToReturn, NumericRange) and
crates/opcua-server/src/status.rs (the StatusCodes of both service sets).
crates/opcua-server/src/client.rs — create_subscription,
modify_subscription, create_monitored_items, modify_monitored_items,
set_monitoring_mode, set_triggering, delete_monitored_items, publish,
publish_acknowledging (→ PublishOutcome), republish,
transfer_subscriptions, delete_subscriptions.
Tests: crates/opcua-server/src/services.rs::tests::subscription_round_trips,
publish_data_change_notification_round_trips;
end-to-end in crates/opcua-server/src/client.rs::tests:
e2e_loopback_subscription_publish (initial value, unchanged cycle, value
change, deletion, then Bad_NoSubscription),
e2e_loopback_modify_subscription_and_monitored_items (revised intervals, the
new client handle reaching the next Publish, an unknown item id giving
Bad_MonitoredItemIdInvalid),
e2e_loopback_set_monitoring_mode_and_delete_monitored_items
(DISABLED discarding the queue, REPORTING resuming it, a second delete giving
Bad_MonitoredItemIdInvalid),
e2e_loopback_set_triggering_releases_a_sampling_item
(a sampling item only reports once linked to the triggering item),
e2e_loopback_republish_and_acknowledgements
(Republish returning the message again, an acknowledgement removing it from the
queue, then Bad_MessageNotAvailable, and an unknown sequence number giving
Bad_SequenceNumberUnknown),
e2e_loopback_transfer_subscriptions_between_sessions
(takeover into a second session, Good_SubscriptionTransferred to the old one,
sendInitialValues in the new session’s first Publish, Bad_NothingToDo for
the session’s own subscription and Bad_SubscriptionIdInvalid for an unknown
one).
Status: done
§7.32/§7.33 Common service headers + common types
Spec: Part 4 §7.32 RequestHeader (p. 150), §7.33 ResponseHeader (p. 151), §7.12 DiagnosticInfo (p. 131), §7.36 SignatureData (p. 153), §7.28 ReadValueId (p. 148), §7.14 EndpointDescription (p. 133), §7.20 MessageSecurityMode (p. 135).
Repo: crates/opcua-uacp/src/securechannel.rs
(RequestHeader, ResponseHeader, null_extension_object, a minimal
DiagnosticInfo skip), crates/opcua-server/src/services.rs
(SignatureData, ReadValueId, header encode/decode against the public
structs), crates/opcua-server/src/wire.rs (String/ByteString/Array/
DiagnosticInfo helpers), MessageSecurityMode/EndpointDescription from
zerodds-opcua-pubsub.
Tests: crates/opcua-uacp/src/securechannel.rs::tests::response_header_with_string_table_round_trips,
crates/opcua-server/src/services.rs::tests::read_response_round_trips.
Status: done
Audit status
16 done / 0 partial / 0 open / 0 blocked / 0 n/a (informative).
Test run: cargo test -p zerodds-opcua-uacp -p zerodds-opcua-server --features zerodds-opcua-server/crypto
— 81 tests green (23 UACP incl. crypto and message chunking + 58 server), 0
failed; including the real opc.tcp end-to-end, a chunked Message over both
transports, secured loopbacks in Sign and SignAndEncrypt, SecureChannel
renewal, all four UserIdentityTokens, the full attribute set, BrowseNext with
continuation points and both the MonitoredItem and Subscription service sets.
Open items: none.
OPC UA Part 4 — Client/Server (UACP + SecureChannel) — Spec-Coverage
Quelle: internal/standards/cache/opcfoundation/opcua-part4-services-1.05.07.pdf
(Services). Cross-Spec:
opcua-part6-mappings-1.05.07.pdf (UACP §7.1, OPC UA Secure Conversation §6.7,
Binär-Codec §5.2),
opcua-part7-profiles-1.05.02.pdf (Transport-Profile, SecurityPolicies)
(alle im selben Cache; aus IP-/Copyright-Gründen nicht im Repo getrackt).
Öffentliche Spec: OPC-UA-Online-Referenz https://reference.opcfoundation.org/ (Part 4 Services, Part 6 Mappings, Part 7 Profiles).
Kontext: Nativer pure-Rust no_std + alloc OPC-UA-Client/Server-Stack —
das Request/Response-Gegenstück zum UADP-PubSub-Stack
(zerodds-opcua-pubsub). Gleiches Vorgehen wie bei den vollen
CORBA-/MQTT-/AMQP-Stacks von ZeroDDS: echter Wire-Stack, nicht nur eine
Gateway-Brücke. Der Part-6-Binär-Codec wird aus zerodds-opcua-pubsub
wiederverwendet. forbid(unsafe_code).
Implementation:
crates/opcua-uacp/· docs.rs — UACP (Hello/Ack/Error/ReverseHello), OPC UA Secure Conversation (OPN/MSG/CLO-Chunks, Security-Header, SequenceHeader, ChannelSecurityToken, Message-Chunking überC/F/Amit Reassembly) und die gesicherten SecurityPolicies (Featurecrypto).crates/opcua-server/· docs.rs — die Service-Sets Discovery, SecureChannel, Session, Attribute, View, Method, MonitoredItem und Subscription, ein in-memory AddressSpace mit Part-3-Attributmodell und Referenzgraph, derServer-Zustandsautomat mit Sessions/UserStore/SecureChannel-Liste, derClientund deropc.tcp-TCP-Transport.
SecurityMode None und die gesicherten SecurityPolicies (RSA-/AES-Krypto,
Basic256Sha256/Aes128_Sha256_RsaOaep/Aes256_Sha256_RsaPss) sind in beiden
Betriebsarten (Sign, SignAndEncrypt) end-to-end implementiert. Offene
Punkte: keine.
Part 6 — UACP, SecureChannel und Binär-Codec
§7.1 OPC UA Connection Protocol (UACP)
Spec: Part 6 §7.1, S. 80 — Message-Struktur (8-Byte-Header
MessageType + ChunkType + MessageSize), Hello/Acknowledge/Error sowie
ReverseHello (§7.1.3 Establishing a connection, S. 83).
Repo: crates/opcua-uacp/src/connection.rs (MessageType, ChunkType,
MessageHeader, HelloMessage, AcknowledgeMessage, ErrorMessage,
ReverseHelloMessage).
Tests: crates/opcua-uacp/src/connection.rs::tests::header_round_trips_and_sizes_match,
acknowledge_round_trips, error_round_trips, reverse_hello_round_trips,
unknown_message_type_rejected, oversized_string_rejected.
Status: done
§6.7 OPC UA Secure Conversation — Chunk-Rahmung und Message-Chunking
Spec: Part 6 §6.7, S. 62 — SecureChannel-Nachrichten als Chunks
(OPN/MSG/CLO), AsymmetricAlgorithmSecurityHeader (OPN) /
SymmetricAlgorithmSecurityHeader (MSG/CLO), SequenceHeader
(§6.7.2 MessageChunk structure). §6.7.1: „OPC UA Secure Conversation will
break OPC UA Messages into several pieces (called ‘MessageChunks’) that are
smaller than the buffer size allowed by the TransportProtocol”; §6.7.2.2
Table 57 IsFinal = C (intermediate) / F (final) / A (abort); §6.7.3:
„The sender indicates that the MessageChunk contains an error by setting the
IsFinal flag to ‘A’” und „the receiver shall ignore the Message but shall not
close the SecureChannel”.
Repo: crates/opcua-uacp/src/securechannel.rs
(SecureChannel::open_chunk / message_chunks / message_chunk /
close_chunk / abort_chunk, set_send_buffer_size +
MIN_SEND_BUFFER_SIZE (§6.7.1: mindestens 8192 Byte bei RSA),
open_incoming mit Reassembly über C-Chunks und ChunkOutcome
(Incomplete/Complete/Abort), MAX_REASSEMBLED_MESSAGE-Deckel,
split_messages, parse_chunk, ParsedChunk, SECURITY_POLICY_NONE);
gesicherte Chunk-Typen über
crates/opcua-uacp/src/crypto.rs::build_symmetric_chunk_typed +
OpenedChunk::chunk_type. Verdrahtung der Puffergröße aus Hello/Acknowledge
in crates/opcua-server/src/server.rs (Server::process) und
crates/opcua-server/src/client.rs (Client::open_channel).
Tests: crates/opcua-uacp/src/securechannel.rs::tests::message_chunk_round_trip,
open_secure_channel_round_trip_none, chunked_message_round_trips_across_c_and_f,
abort_chunk_reports_the_error_and_keeps_the_channel,
split_messages_splits_by_message_size;
End-to-End über beide Transporte:
crates/opcua-server/src/client.rs::tests::e2e_loopback_message_larger_than_the_buffer_is_chunked,
e2e_tcp_message_larger_than_the_buffer_is_chunked.
Status: done
§6.7 SecureChannel-Krypto — gesicherte SecurityPolicies
Spec: Part 6 §6.7.2 (Chunk-Security-Layout: SequenceHeader‖Body‖Padding‖
Signature, Signatur über MessageHeader…Padding, danach verschlüsselt),
§6.7.6 Deriving keys (S. 70, P-SHA256); Part 7 SecurityPolicies
Basic256Sha256, Aes128_Sha256_RsaOaep, Aes256_Sha256_RsaPss.
Repo (Krypto-Kern): crates/opcua-uacp/src/crypto.rs (Feature crypto):
SecurityPolicy (URI/Algo-Parameter), p_sha256 + derive_keys (§6.7.6:
Signing/Encrypting/IV), symmetrisch build_symmetric_chunk/
open_symmetric_chunk (AES-128/256-CBC + HMAC-SHA256, Padding, Sign &
SignAndEncrypt), asymmetrisch build_asymmetric_chunk/open_asymmetric_chunk
(RSA-OAEP-SHA1/SHA256 + RSA-PKCS#1-v1.5/PSS über SHA-256, block-weise OPN-Krypto
mit Padding/ExtraPaddingByte + Zertifikat-Thumbprint via sha1_thumbprint).
Repo (Verdrahtung): crates/opcua-uacp/src/securechannel.rs
(SecuritySession, SecureChannel::install_security/open_incoming,
gesicherte message_chunk/close_chunk), crates/opcua-server/src/server.rs
(ServerSecurity, Server::set_security, open_secure_channel_secured:
OPN öffnen → Nonce → Key-Derivation → gesicherte OPN-Antwort + Session-Install,
ISSUE wie RENEW),
crates/opcua-server/src/client.rs (ClientSecurity, Client::set_security,
open_secure_channel_secured). Trust-Store = übergebene Peer-Zertifikate/
-Public-Keys; RNG Caller-bereitgestellt (Box<dyn CryptoRngCore + Send>,
OS-RNG unter std).
Tests: crates/opcua-uacp/src/crypto.rs::tests::p_sha256_is_deterministic_and_sized,
derive_keys_lengths_and_mirror, symmetric_sign_and_encrypt_round_trip
(alle 3 Policies), symmetric_sign_only_round_trip, symmetric_tamper_is_rejected,
asymmetric_round_trip_oaep_pkcs15, asymmetric_round_trip_oaep_sha256_pss,
asymmetric_wrong_signer_is_rejected, full_secured_handshake_flow;
gesicherter End-to-End je Betriebsart:
crates/opcua-server/src/client.rs::tests::e2e_secured_loopback_sign_and_encrypt
(echte RSA-Keys + OS-RNG: verschlüsselter+signierter Handshake → Read/Call/Write
über Basic256Sha256/SignAndEncrypt) und
e2e_secured_loopback_sign_only_and_renew (Aes256_Sha256_RsaPss/Sign:
Handshake → Read, EndpointDescription mit MessageSecurityMode::Sign und
Policy-URI, dann RENEW über den gesicherten Kanal).
Status: done
§5.2 OPC UA Binary — Codec-Wiederverwendung
Spec: Part 6 §5.2, S. 18 — Binär-Kodierung (Built-in-Typen, NodeId, Variant, DataValue, ExtensionObject, Arrays).
Repo: wiederverwendet aus crates/opcua-pubsub/src/binary/ über
UaReader/UaWriter/UaEncode/UaDecode; NodeId/Variant/DataValue/
ExtensionObject aus zerodds-opcua-gateway.
Tests: crates/opcua-pubsub/src/binary/builtin.rs::tests (20 Tests), darunter
nodeid_two_byte_compact_form, guid_encodes_data1_little_endian,
variant_scalar_int32, variant_multidim_array, data_value_full;
crates/opcua-pubsub/src/binary/io.rs::tests::primitive_roundtrip_is_little_endian,
::read_past_end_reports_eof, ::floats_roundtrip;
crates/opcua-pubsub/src/binary/mod.rs::tests::read_array_rejects_oversized_length,
::read_string_array_roundtrip_within_bound.
Status: done
§7.2 OPC UA TCP — opc.tcp-Transport (End-to-End)
Spec: Part 6 §7.2, S. 86 — UA-TCP-Mapping; Transport-Profil
uatcp-uasc-uabinary (Part 7).
Repo: crates/opcua-server/src/client.rs
(TcpTransport, serve_connection, Transport-Trait, LoopbackTransport),
crates/opcua-server/src/server.rs (Server::process).
Tests: crates/opcua-server/src/client.rs::tests::e2e_tcp_connect_read_call
(echter opc.tcp-Socket: Connect/Read/Call über TcpListener+Thread),
e2e_loopback_connect_read_call, e2e_loopback_write_then_read.
Status: done
Part 4 — Service Sets
§5.6.2 OpenSecureChannel
Spec: Part 4 §5.6.2, S. 21 f. — OpenSecureChannelRequest/Response,
SecurityTokenRequestType (Table 11: „ISSUE creates a new SecurityToken for a
new SecureChannel. RENEW creates a new SecurityToken for an existing
SecureChannel”), secureChannelId („shall be null when creating a new
SecureChannel”), ChannelSecurityToken (RevisedLifetime). §5.6.2.1: „Clients
should request a new SecurityToken after 75 % of its lifetime has elapsed” und
„A Server application should limit the number of SecureChannels … the Server
shall close the oldest unused SecureChannel”.
Repo: crates/opcua-uacp/src/securechannel.rs
(OpenSecureChannelRequest, OpenSecureChannelResponse,
SecurityTokenRequestType, ChannelSecurityToken, SecureChannel::adopt_token
/ reset_sequences), crates/opcua-server/src/server.rs
(Server::open_secure_channel und open_secure_channel_secured mit ISSUE- und
RENEW-Zweig, Server::channels als Liste offener SecureChannels mit
MAX_SECURE_CHANNELS + Verdrängung des ältesten, chunk_channel_id wählt den
Kanal je Chunk aus der SecureChannelId des Headers),
crates/opcua-server/src/client.rs (Client::renew_secure_channel,
open_secure_channel_plain/open_secure_channel_secured mit
SecurityTokenRequestType-Parameter — die RENEW-OPN trägt die eigene
SecureChannelId, die ISSUE-OPN die Null-Id).
Tests: crates/opcua-uacp/src/securechannel.rs::tests::open_response_round_trip,
crates/opcua-server/src/client.rs::tests::e2e_loopback_connect_read_call
(durchläuft OPN), e2e_loopback_secure_channel_renew_keeps_the_channel_working
(RENEW im Modus None: Session und Read überleben den Token-Wechsel),
e2e_secured_loopback_sign_only_and_renew (RENEW über den gesicherten Kanal),
e2e_loopback_transfer_subscriptions_between_sessions (zwei Clients, zwei
gleichzeitig offene SecureChannels auf demselben Server).
Status: done
§5.7.2 CreateSession
Spec: Part 4 §5.7.2, S. 24 — CreateSessionRequest/Response
(SessionId, AuthenticationToken, RevisedSessionTimeout, ServerEndpoints,
ServerSignature).
Repo: crates/opcua-server/src/services.rs
(CreateSessionRequest, CreateSessionResponse),
crates/opcua-server/src/server.rs (Server::create_session: legt die
Session an, zieht die 32-Byte-serverNonce (session_nonce, unter crypto
aus dem konfigurierten CSPRNG) und liefert serverCertificate +
EndpointDescription mit den UserTokenPolicys des UserStore).
Tests: crates/opcua-server/src/client.rs::tests::e2e_loopback_connect_read_call,
e2e_tcp_connect_read_call, e2e_loopback_user_name_identity_token.
Status: done
§5.7.3 ActivateSession — UserIdentityTokens
Spec: Part 4 §5.7.3, S. 29 ff. — ActivateSessionRequest/Response
(ClientSignature, LocaleIds, UserIdentityToken, UserTokenSignature,
ServerNonce). §5.7.3.1: „Once used, a serverNonce cannot be used again. For
that reason, the Server returns a new serverNonce each time the
ActivateSession Service is called” und „Clients can change the identity of a
user associated with a Session by calling the ActivateSession Service”.
§5.7.3.2: „Null or empty user token shall always be interpreted as anonymous”,
serverNonce „shall have a length between 32 and 128 bytes inclusive. The Client
shall check the length”. §7.40 Table 181 — die vier UserIdentityTokens
(AnonymousIdentityToken §7.40.3, UserNameIdentityToken §7.40.4,
X509IdentityToken §7.40.5, IssuedIdentityToken §7.40.6); §7.41
UserTokenPolicy, §7.42 UserTokenType. Ergebnis-Codes §5.7.3.3:
Bad_IdentityTokenInvalid, Bad_IdentityTokenRejected,
Bad_UserAccessDenied, Bad_UserSignatureInvalid.
Repo: crates/opcua-server/src/identity.rs (UserIdentityToken mit allen
vier Table-181-Typen und ihren _Encoding_DefaultBinary-NodeIds in
token_ids, UserStore mit add_user/add_certificate/add_issued_token/
set_allow_anonymous und validate, user_token_policy für die §7.41-Policy
in der EndpointDescription, ClientUserId),
crates/opcua-server/src/services.rs (ActivateSessionRequest,
ActivateSessionResponse), crates/opcua-server/src/server.rs
(Server::set_user_store, Session-Liste mit activated/user/nonce,
activate_session prüft das Token und zieht je Aufruf eine neue Nonce,
require_activated_session weist jeden Session-Dienst vor der Aktivierung mit
Bad_SessionNotActivated ab), crates/opcua-server/src/client.rs
(Client::set_identity, change_user, user_token_signature — die
§6.1.8-Signatur über serverCertificate‖serverNonce via
crates/opcua-uacp/src/crypto.rs::sign_with_policy/signature_data).
Tests: crates/opcua-server/src/identity.rs::tests::all_four_table_181_tokens_round_trip_through_the_extension_object,
null_token_is_anonymous, user_token_policy_round_trips,
store_accepts_each_token_type_and_rejects_bad_credentials,
anonymous_is_rejected_when_the_server_does_not_allow_it;
crates/opcua-server/src/services.rs::tests::session_lifecycle_round_trips;
End-to-End crates/opcua-server/src/client.rs::tests::e2e_loopback_user_name_identity_token
(publizierte USERNAME-Policy, Anonymous abgelehnt, falsches Passwort
Bad_UserAccessDenied, richtiges aktiviert) und
e2e_loopback_x509_and_issued_identity_tokens_and_user_change
(X.509- und IssuedToken-Aktivierung, Identitätswechsel auf der laufenden
Session, gefälschtes Token und unbekanntes Zertifikat abgelehnt),
e2e_loopback_session_must_be_activated_and_close_deletes_subscriptions.
Status: done
§5.7.4 CloseSession
Spec: Part 4 §5.7.4, S. 31 — CloseSessionRequest (DeleteSubscriptions)
/Response.
Repo: crates/opcua-server/src/services.rs
(CloseSessionRequest, CloseSessionResponse).
Tests: crates/opcua-server/src/services.rs::tests::session_lifecycle_round_trips.
Status: done
§5.11.2 Read — alle Attribute des Adressraums
Spec: Part 4 §5.11.2, S. 45 ff. — „This Service is used to read one or more
Attributes of one or more Nodes”; ReadRequest (MaxAge, TimestampsToReturn,
ReadValueId[]) / ReadResponse (DataValue[]). §5.11.2.2: „Negative values
are invalid for maxAge”; indexRange nach §7.27 / Annex A.3; §7.39
TimestampsToReturn. Ergebnis-Codes §5.11.2.3/.4: Bad_MaxAgeInvalid,
Bad_TimestampsToReturnInvalid, Bad_NothingToDo, Bad_NodeIdUnknown,
Bad_AttributeIdInvalid, Bad_IndexRangeInvalid, Bad_IndexRangeNoData,
Bad_NotReadable, Bad_DataEncodingInvalid. Die Attribut-Ids stehen in
Part 6 Annex A.1 Table A.1 (1 NodeId … 27 AccessLevelEx), die
NodeClass-Zuordnung in Part 3 §5.
Repo: crates/opcua-server/src/attributes.rs (attribute_ids = Table A.1,
ClassAttributes mit den Part-3-Attributsätzen je NodeClass, access_level-
Bits nach Part 3 §8.57, NumericRange (Parser + apply) nach §7.27/Annex A.3,
TimestampsToReturn), crates/opcua-server/src/address_space.rs
(NodeMeta mit Description/WriteMask/UserWriteMask/ClassAttributes,
AddressSpace::read_attribute liefert jedes Attribut der NodeClass und
Bad_AttributeIdInvalid für jedes andere), crates/opcua-server/src/status.rs
(die StatusCodes), crates/opcua-server/src/services.rs
(ReadRequest, ReadResponse, ReadValueId),
crates/opcua-server/src/server.rs (Server::read prüft maxAge und
TimestampsToReturn, read_one wendet indexRange und dataEncoding an),
crates/opcua-server/src/client.rs (Client::read_attributes,
read_attribute, read_values, read_value_id).
Tests: crates/opcua-server/src/attributes.rs::tests::numeric_range_parses_the_annex_a3_grammar,
numeric_range_rejects_the_forms_the_spec_calls_invalid,
numeric_range_apply_selects_and_clamps,
timestamps_to_return_strips_what_was_not_asked_for;
crates/opcua-server/src/services.rs::tests::read_request_round_trips,
read_response_round_trips;
End-to-End crates/opcua-server/src/client.rs::tests::e2e_loopback_reads_every_attribute_of_a_variable
(16 Attribute einer Variable, jedes Good mit geprüftem Wert; Executable
und eine Id außerhalb Table A.1 als Bad_AttributeIdInvalid),
e2e_loopback_index_range_read_and_write,
e2e_loopback_read_honours_timestamps_to_return_and_max_age,
read_unknown_node_yields_bad_status.
Status: done
§5.11.4 Write — alle beschreibbaren Attribute
Spec: Part 4 §5.11.4, S. 50 ff. — „This Service is used to write values to
one or more Attributes of one or more Nodes”; attributeId „shall be a
valid Attribute id … The IntegerIds for the Attributes are defined in
OPC 10000-6”; indexRange nach §7.27; „If the SourceTimestamp or the
ServerTimestamp is specified, the Server shall use these values. The Server
returns a Bad_WriteNotSupported error if it does not support writing of
timestamps.” Ergebnis-Codes §5.11.4.4: Bad_NodeIdUnknown,
Bad_AttributeIdInvalid, Bad_IndexRangeInvalid, Bad_IndexRangeNoData,
Bad_WriteNotSupported, Bad_NotWritable, Bad_TypeMismatch. Welches
Attribut beschreibbar ist, steht in Part 3 §8.60 Table 44 (AttributeWriteMask,
Bits 0–25) — mit dem ausdrücklichen Sonderfall, dass das Value-Attribut einer
Variable nicht in der Maske steht, sondern von AccessLevel/UserAccessLevel
(Part 3 §8.57) gesteuert wird.
Repo: crates/opcua-server/src/attributes.rs (write_mask-Bits = Table 44,
write_mask_bit bildet Attribut-Id auf Bit ab und gibt für Value bewusst
None zurück), crates/opcua-server/src/address_space.rs
(AddressSpace::write_attribute: WriteMask ∧ UserWriteMask, dann
write_value_attribute mit CurrentWrite/TimestampWrite/StatusWrite
bzw. ValueForVariableType, write_class_attribute je NodeClass;
Bad_TypeMismatch bei falschem Builtin-Typ, Bad_WriteNotSupported für
NodeId/NodeClass), crates/opcua-server/src/services.rs
(WriteRequest, WriteResponse, WriteValue),
crates/opcua-server/src/server.rs (Server::write_one löst indexRange
gegen den aktuellen Wert auf), crates/opcua-server/src/client.rs
(Client::write_attributes, write_values).
Tests: crates/opcua-server/src/attributes.rs::tests::write_mask_bit_matches_table_44,
numeric_range_replace_writes_in_place;
crates/opcua-server/src/services.rs::tests::write_round_trips;
End-to-End crates/opcua-server/src/client.rs::tests::e2e_loopback_write_respects_the_write_mask_and_access_level
(gesetztes Maskenbit schreibt, gelöschtes gibt Bad_NotWritable, fehlendes
CurrentWrite gibt Bad_NotWritable, falscher Typ gibt Bad_TypeMismatch,
der abgelehnte Schreibvorgang lässt den Wert unverändert),
e2e_loopback_index_range_read_and_write, e2e_loopback_write_then_read.
Status: done
§5.12.2 Call
Spec: Part 4 §5.12.2, S. 54 — CallRequest (CallMethodRequest[]) /
CallResponse (CallMethodResult[]).
Repo: crates/opcua-server/src/services.rs
(CallMethodRequest, CallMethodResult, CallRequest, CallResponse),
crates/opcua-server/src/address_space.rs (Method-Handler-Registry),
crates/opcua-server/src/client.rs (Client::call_method).
Tests: crates/opcua-server/src/services.rs::tests::call_round_trips,
crates/opcua-server/src/address_space.rs::tests::method_call_dispatch,
crates/opcua-server/src/client.rs::tests::e2e_loopback_connect_read_call,
e2e_tcp_connect_read_call.
Status: done
§5.9.2 / §5.9.3 Browse + BrowseNext (View Service Set)
Spec: Part 4 §5.9.2 Browse (S. 38 ff.), §5.9.3 BrowseNext (S. 40 ff.),
§7.29 ReferenceDescription (S. 148), §7.6 BrowseResult (S. 118), §7.9
ContinuationPoint (S. 129), §7.44 ViewDescription (S. 169); ExpandedNodeId
Part 6 §5.2.2.10. §5.9.2.2 requestedMaxReferencesPerNode: „Indicates the
maximum number of references to return for each starting Node … The value 0
indicates that the Client is imposing no limitation.” §5.9.3.1: „This Service
is used to request the next set of Browse or BrowseNext response information
that is too large to be sent in a single response.” §5.9.3.2
releaseContinuationPoints: „TRUE — passed continuationPoints shall be reset
to free resources in the Server. The continuation points are released and the
results and diagnosticInfos arrays are empty.” Ergebnis-Codes §5.9.2.4/§5.9.3.4:
Bad_NodeIdUnknown, Bad_BrowseDirectionInvalid, Bad_ReferenceTypeIdInvalid,
Bad_NoContinuationPoints, Bad_ContinuationPointInvalid, Bad_NothingToDo.
Repo: crates/opcua-server/src/address_space.rs (Knoten-/Referenz-Modell:
NodeMeta, ReferenceRecord, NodeClass, reference_types-Konstanten ns0,
add_node/add_reference/node_meta/browse mit Richtung +
Reference-Subtype-Filter (includeSubtypes) + NodeClass-Maske),
crates/opcua-server/src/services.rs (ViewDescription, BrowseDescription,
ReferenceDescription, BrowseResult, BrowseRequest/BrowseResponse,
BrowseNextRequest/BrowseNextResponse (NodeIds 533/536),
ExpandedNodeId-Wire); crates/opcua-server/src/server.rs
(browse_one: Reference-Filter + resultMask-Projektion via
project_reference, Bad_NodeIdUnknown für unbekannte Knoten,
Bad_BrowseDirectionInvalid und Bad_ReferenceTypeIdInvalid per
is_known_reference_type, Kappung auf requestedMaxReferencesPerNode mit
allocate_continuation_point; browse_next_one setzt fort oder gibt frei;
ContinuationPoint-Liste mit MAX_CONTINUATION_POINTS →
Bad_NoContinuationPoints), crates/opcua-server/src/client.rs
(Client::browse, browse_limited, browse_next, browse_all).
Tests: crates/opcua-server/src/address_space.rs::tests::browse_forward_and_inverse,
browse_subtype_filter_excludes_non_hierarchical, browse_node_class_mask_filters_targets,
crates/opcua-server/src/services.rs::tests::browse_round_trips,
crates/opcua-server/src/client.rs::tests::e2e_loopback_browse
(Objects→Boiler→Temperature über Organizes/HasComponent) und
e2e_loopback_browse_next_walks_continuation_points (fünf Referenzen,
requestedMaxReferencesPerNode = 2 → erster Batch + Continuation-Point,
BrowseNext liefert die restlichen drei in Reihenfolge, release gibt den Punkt
frei und ein zweiter Zugriff darauf ist Bad_ContinuationPointInvalid,
browse_all läuft die Kette bei Batchgröße 1 bis zum Ende ab).
Status: done
§5.5.4 / §5.5.2 Discovery — GetEndpoints + FindServers
Spec: Part 4 §5.5.4 GetEndpoints (S. 14), §5.5.2 FindServers (S. 12) — session-lose Discovery-Services (nur SecureChannel, keine Session nötig).
Repo: crates/opcua-server/src/services.rs
(GetEndpointsRequest/GetEndpointsResponse (NodeIds 426/429),
FindServersRequest/FindServersResponse (NodeIds 422/425)),
crates/opcua-server/src/server.rs (Dispatch-Arme: GetEndpoints liefert die
EndpointDescription des Servers — Server::endpoint mit den
UserTokenPolicys aus dem UserStore und, unter crypto, dem tatsächlich
konfigurierten MessageSecurityMode samt Policy-URI (endpoint_security) —,
FindServers seine ApplicationDescription),
crates/opcua-server/src/client.rs (Client::open_channel — Hello+OPN ohne
Session — plus Client::get_endpoints/find_servers).
Tests: crates/opcua-server/src/services.rs::tests::discovery_requests_round_trip,
crates/opcua-server/src/client.rs::tests::e2e_loopback_discovery
(session-loser Channel → GetEndpoints liefert Endpoint mit
SecurityPolicy None, FindServers liefert den Server);
e2e_secured_loopback_sign_only_and_renew prüft denselben Endpoint mit
Aes256_Sha256_RsaPss/Sign, e2e_loopback_user_name_identity_token die
publizierte USERNAME-UserTokenPolicy.
Status: done
§5.13 / §5.14 Subscription + MonitoredItem Service Sets
Spec: Part 4 §5.13.2 CreateMonitoredItems (S. 63), §5.13.3 ModifyMonitoredItems (S. 65), §5.13.4 SetMonitoringMode (S. 67), §5.13.5 SetTriggering (S. 68), §5.13.6 DeleteMonitoredItems (S. 69), §5.14.2 CreateSubscription (S. 76), §5.14.3 ModifySubscription (S. 78), §5.14.4 SetPublishingMode (S. 79), §5.14.5 Publish (S. 81), §5.14.6 Republish (S. 83), §5.14.7 TransferSubscriptions (S. 83), §5.14.8 DeleteSubscriptions (S. 85); §7.21 MonitoringParameters, §7.23 MonitoringMode, §7.25.2 DataChangeNotification (S. 145), §7.25.4 StatusChangeNotification (S. 146), §7.26 NotificationMessage (S. 147), §7.39 TimestampsToReturn.
Die Sampling-Regeln, gegen die dieser Server misst, stehen in §5.14.1: die Subscription „attempts to send a NotificationMessage” pro Zyklus (b), ein Zyklus ohne Notifications liefert eine Keep-Alive-Nachricht (f), die Session hält eine „retransmission queue of sent NotificationMessages … until they are acknowledged” (i). §5.13.4.1: „Setting the mode to DISABLED causes all queued Notifications to be deleted.” §5.13.6.1: „When a MonitoredItem is deleted, its triggered item links are also deleted.” §5.14.3.2: „If the requested value is 0 or negative, the Server shall revise with the fastest supported publishing interval”, „The lifetime count shall be a minimum of three times the keep-alive count”. §5.14.7.1: „the Server shall issue a StatusChangeNotification notificationMessage with the status code Good_SubscriptionTransferred to the old Session”.
Repo: crates/opcua-server/src/services.rs — die zwölf Dienste beider
Service-Sets als Wire-Typen: CreateSubscriptionRequest/Response,
ModifySubscriptionRequest/Response (793/796), SetPublishingModeRequest/
Response, PublishRequest/Response, RepublishRequest/Response
(832/835), TransferSubscriptionsRequest/Response + TransferResult
(841/844/838), DeleteSubscriptionsRequest/Response,
CreateMonitoredItemsRequest/Response, ModifyMonitoredItemsRequest/
Response + MonitoredItemModifyRequest/MonitoredItemModifyResult
(763/766/757/760), SetMonitoringModeRequest/Response (769/772),
SetTriggeringRequest/Response (775/778), DeleteMonitoredItemsRequest/
Response (781/784), dazu MonitoringParameters, SubscriptionAcknowledgement,
MonitoredItemNotification, DataChangeNotification (811) und
StatusChangeNotification (820).
crates/opcua-server/src/server.rs — der Subscription-Zustand: Subscription
mit revidierten Parametern (revise_publishing_interval/revise_keep_alive/
revise_lifetime nach §5.14.3.2), retransmission_queue der Tiefe
RETRANSMISSION_QUEUE_DEPTH, MonitoredItem mit MonitoringMode,
queue/queue_size/discard_oldest (§5.13.1.5, trim_queue),
index_range, timestamps_to_return und triggered_items (§5.13.5);
Server::publish verarbeitet erst die Acknowledgements, meldet einer Session,
deren Subscription übernommen wurde, die §7.25.4-StatusChangeNotification,
sampelt dann über sample_subscription/sample_item und stellt die Queues der
Reporting-Items samt der von ihnen getriggerten Sampling-Items zu, begrenzt
durch max_notifications_per_publish (more_notifications); ohne Notification
geht eine Keep-Alive-Nachricht hinaus, ohne Subscription der Service-Fehler
Bad_NoSubscription. crates/opcua-server/src/attributes.rs
(MonitoringMode, TimestampsToReturn, NumericRange) und
crates/opcua-server/src/status.rs (die StatusCodes der beiden Service-Sets).
crates/opcua-server/src/client.rs — create_subscription,
modify_subscription, create_monitored_items, modify_monitored_items,
set_monitoring_mode, set_triggering, delete_monitored_items, publish,
publish_acknowledging (→ PublishOutcome), republish,
transfer_subscriptions, delete_subscriptions.
Tests: crates/opcua-server/src/services.rs::tests::subscription_round_trips,
publish_data_change_notification_round_trips;
End-to-End in crates/opcua-server/src/client.rs::tests:
e2e_loopback_subscription_publish (Erstwert, unveränderter Zyklus,
Wertwechsel, Löschen, danach Bad_NoSubscription),
e2e_loopback_modify_subscription_and_monitored_items (revidierte Intervalle,
neuer Client-Handle wirkt im nächsten Publish, unbekannte Item-Id ist
Bad_MonitoredItemIdInvalid),
e2e_loopback_set_monitoring_mode_and_delete_monitored_items
(DISABLED verwirft die Queue, REPORTING nimmt sie wieder auf, doppeltes
Löschen ist Bad_MonitoredItemIdInvalid),
e2e_loopback_set_triggering_releases_a_sampling_item
(ein Sampling-Item meldet erst, nachdem es an das triggernde Item gebunden
wurde), e2e_loopback_republish_and_acknowledgements
(Republish liefert die Nachricht erneut, ein Acknowledgement entfernt sie aus
der Queue, danach Bad_MessageNotAvailable, eine unbekannte Sequenznummer
ist Bad_SequenceNumberUnknown),
e2e_loopback_transfer_subscriptions_between_sessions
(Übernahme in eine zweite Session, Good_SubscriptionTransferred an die alte,
sendInitialValues im ersten Publish der neuen, Bad_NothingToDo für die
eigene und Bad_SubscriptionIdInvalid für eine unbekannte Subscription).
Status: done
§7.32/§7.33 Gemeinsame Service-Header + Common-Types
Spec: Part 4 §7.32 RequestHeader (S. 150), §7.33 ResponseHeader (S. 151), §7.12 DiagnosticInfo (S. 131), §7.36 SignatureData (S. 153), §7.28 ReadValueId (S. 148), §7.14 EndpointDescription (S. 133), §7.20 MessageSecurityMode (S. 135).
Repo: crates/opcua-uacp/src/securechannel.rs
(RequestHeader, ResponseHeader, null_extension_object, minimaler
DiagnosticInfo-Skip), crates/opcua-server/src/services.rs
(SignatureData, ReadValueId, Header-Encode/Decode gegen die Public-Structs),
crates/opcua-server/src/wire.rs (String/ByteString/Array/DiagnosticInfo-Hilfen),
MessageSecurityMode/EndpointDescription aus zerodds-opcua-pubsub.
Tests: crates/opcua-uacp/src/securechannel.rs::tests::response_header_with_string_table_round_trips,
crates/opcua-server/src/services.rs::tests::read_response_round_trips.
Status: done
Audit-Status
16 done / 0 partial / 0 open / 0 blocked / 0 n/a (informative).
Test-Lauf: cargo test -p zerodds-opcua-uacp -p zerodds-opcua-server --features zerodds-opcua-server/crypto
— 81 Tests grün (23 UACP inkl. Krypto und Message-Chunking + 58 Server), 0
failed; inkl. echtem opc.tcp-End-to-End, gechunkter Nachricht über beide
Transporte, gesicherten Loopbacks in Sign und SignAndEncrypt,
SecureChannel-Renew, allen vier UserIdentityTokens, dem vollen Attributsatz,
BrowseNext mit Continuation-Points sowie beiden Service-Sets für
MonitoredItems und Subscriptions.
Offene Punkte: keine.