[F28] mxaccess-asb: canonical XML signing for all 8 remaining ops
rust / build / test / clippy / fmt (push) Has been cancelled
rust / build / test / clippy / fmt (push) Has been cancelled
Closes F28. The 5 [XmlSerializerFormat] ops landed in commit f14580e
(2026-05-05); this commit closes out the remaining 8 ConnectedRequest
shapes, eliminating the legacy NBFX-bytes signing fallback from every
`client::*` op.
Two deliverables:
1. Extended `MxAsbClient.Probe --dump-signed-xml` (.NET probe) to
emit deterministic canonical-XML output for ReadRequest,
WriteBasicRequest, PublishWriteCompleteRequest,
CreateSubscriptionRequest, DeleteSubscriptionRequest,
AddMonitoredItemsRequest, DeleteMonitoredItemsRequest,
PublishRequest. Saved 8 fixtures at
rust/crates/mxaccess-asb/tests/fixtures/signed-xml/*.xml. Pinned
field values for reproducibility:
- SubscriptionId = 0x1234_5678_9abc_def0
- MaxQueueSize = 100, SampleInterval = 1000
- WriteHandle = 0xDEAD_BEEF
- WriteValue = Variant.FromInt32(42)
- MonitoredItem with the existing sample-item shape
2. Ported 8 emitters in mxaccess-asb::xml_canonical:
emit_read_request_xml, emit_write_basic_request_xml,
emit_publish_write_complete_request_xml,
emit_create_subscription_request_xml,
emit_delete_subscription_request_xml,
emit_add_monitored_items_request_xml,
emit_delete_monitored_items_request_xml,
emit_publish_request_xml.
New helpers consolidate XmlSerializer's per-namespace shapes:
- emit_invensys_text — primitive int/long fields in the parent
urn:invensys.schemas namespace (no xmlns redeclaration).
- emit_write_value — <Values> wrapper inlining
Value (Variant), Status (default AsbStatus), Comment (xsi:nil).
- emit_monitored_item — <Items> wrapper inlining
Item, SampleInterval, ValueDeadband, UserData, Buffered.
- emit_inline_item_identity — ItemIdentity rendered as a child
of MonitoredItem (single xmlns redeclaration on the wrapper,
children inherit).
- emit_inline_text + emit_inline_optional_string —
no-redeclaration variants of emit_iom_text +
emit_iom_optional_string.
- emit_idata_variant — Variant's Type/Length/Payload children
in the http://asb.contracts.idata.data/20111111 namespace
(Payload self-closes with xsi:nil when Length=0).
- emit_iom_default_variant — wrapper for ValueDeadband / UserData
(default-shape Variant in iom:2 namespace).
New private helper AsbClient::pre_signing_validator() consolidates
the 8 callsite repetitions of (connection_id,
peek_next_message_number, "", "").
Wired into client::* — every send_signed_envelope[_one_way] call now
passes Some(&xml) for xml_for_signing. The 8 ops affected: read,
write, publish_write_complete, delete_monitored_items,
create_subscription, add_monitored_items, publish,
delete_subscription (plus their _once retry-loop variants).
8 new fixture-comparison tests (mxaccess-asb 87 → 95). Each emitter
byte-equal vs the .NET fixture on the first try — no iteration
needed. Workspace clippy clean.
Live verification: `cargo run -p mxaccess --example asb-subscribe`
returns TestChildObject.TestInt = 99 against AVEVA — proving Read
(now signed via canonical XML) round-trips end-to-end where it
previously used the legacy NBFX-bytes path.
The remaining 7 ops are wire-tested at fixture-byte-equality only;
live exercise is gated on the F33 follow-on capture for
subscribe-flow ops, but the canonical XML matches the .NET reference
byte-for-byte, so the HMAC will match by construction once the
session is in a state to issue those ops.
design/followups.md:
- F28 moved to Resolved with the full two-step audit trail.
- F18 M5 status block rewritten — all sub-followups (F26 stream,
F28, F29, F32, F33) now closed. M5 DoD bullets 1+2+3+4 all green.
- tests/fixtures/signed-xml/README.md updated to list the 8 new
fixtures + their pinned input values.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -107,6 +107,23 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> AsbClient<T> {
|
||||
&mut self.authenticator
|
||||
}
|
||||
|
||||
/// Build the pre-signing [`ConnectionValidator`] for an upcoming
|
||||
/// signed op: `connection_id` + the next-allocated `message_number`,
|
||||
/// with empty `mac_base64` / `iv_base64` (the canonical-XML emitter
|
||||
/// produces self-closing `<MessageAuthenticationCode />` and
|
||||
/// `<SignatureInitializationVector />` for empty `byte[]`, matching
|
||||
/// .NET's `XmlSerializer` shape at HMAC time). The MAC + IV get
|
||||
/// filled in by `AsbAuthenticator::sign` after the XML bytes have
|
||||
/// been hashed.
|
||||
fn pre_signing_validator(&mut self) -> ConnectionValidator {
|
||||
ConnectionValidator {
|
||||
connection_id: self.authenticator.connection_id(),
|
||||
message_number: self.authenticator.peek_next_message_number(),
|
||||
mac_base64: String::new(),
|
||||
iv_base64: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the canonical NMF preamble (`Version 1.0` → `Duplex` →
|
||||
/// `Via` → `BinaryWithDictionary` → `PreambleEnd`) and read the
|
||||
/// peer's `PreambleAck` reply. Records other than `PreambleAck` —
|
||||
@@ -462,9 +479,11 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> AsbClient<T> {
|
||||
}
|
||||
|
||||
async fn read_once(&mut self, items: &[ItemIdentity]) -> Result<ReadResponse, ClientError> {
|
||||
let pre_signing = self.pre_signing_validator();
|
||||
let xml = crate::xml_canonical::emit_read_request_xml(&pre_signing, items);
|
||||
let body = build_read_request_body(items);
|
||||
let response = self
|
||||
.send_signed_envelope(actions::READ, body, None, false)
|
||||
.send_signed_envelope(actions::READ, body, Some(&xml), false)
|
||||
.await?;
|
||||
Ok(decode_read_response(&response.body_tokens)?)
|
||||
}
|
||||
@@ -478,9 +497,11 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> AsbClient<T> {
|
||||
pub async fn publish_write_complete(
|
||||
&mut self,
|
||||
) -> Result<PublishWriteCompleteResponse, ClientError> {
|
||||
let pre_signing = self.pre_signing_validator();
|
||||
let xml = crate::xml_canonical::emit_publish_write_complete_request_xml(&pre_signing);
|
||||
let body = build_publish_write_complete_request_body();
|
||||
let response = self
|
||||
.send_signed_envelope(actions::PUBLISH_WRITE_COMPLETE, body, None, false)
|
||||
.send_signed_envelope(actions::PUBLISH_WRITE_COMPLETE, body, Some(&xml), false)
|
||||
.await?;
|
||||
Ok(decode_publish_write_complete_response(
|
||||
&response.body_tokens,
|
||||
@@ -494,9 +515,15 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> AsbClient<T> {
|
||||
subscription_id: i64,
|
||||
items: &[MinimalMonitoredItem],
|
||||
) -> Result<DeleteMonitoredItemsResponse, ClientError> {
|
||||
let pre_signing = self.pre_signing_validator();
|
||||
let xml = crate::xml_canonical::emit_delete_monitored_items_request_xml(
|
||||
&pre_signing,
|
||||
subscription_id,
|
||||
items,
|
||||
);
|
||||
let body = build_delete_monitored_items_request_body(subscription_id, items);
|
||||
let response = self
|
||||
.send_signed_envelope(actions::DELETE_MONITORED_ITEMS, body, None, false)
|
||||
.send_signed_envelope(actions::DELETE_MONITORED_ITEMS, body, Some(&xml), false)
|
||||
.await?;
|
||||
Ok(decode_delete_monitored_items_response(
|
||||
&response.body_tokens,
|
||||
@@ -517,9 +544,16 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> AsbClient<T> {
|
||||
values: &[MinimalWriteValue],
|
||||
write_handle: u32,
|
||||
) -> Result<WriteResponse, ClientError> {
|
||||
let pre_signing = self.pre_signing_validator();
|
||||
let xml = crate::xml_canonical::emit_write_basic_request_xml(
|
||||
&pre_signing,
|
||||
items,
|
||||
values,
|
||||
write_handle,
|
||||
);
|
||||
let body = build_write_request_body(items, values, write_handle);
|
||||
let response = self
|
||||
.send_signed_envelope(actions::WRITE, body, None, false)
|
||||
.send_signed_envelope(actions::WRITE, body, Some(&xml), false)
|
||||
.await?;
|
||||
Ok(decode_write_response(&response.body_tokens)?)
|
||||
}
|
||||
@@ -564,9 +598,15 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> AsbClient<T> {
|
||||
max_queue_size: i64,
|
||||
sample_interval: u64,
|
||||
) -> Result<CreateSubscriptionResponse, ClientError> {
|
||||
let pre_signing = self.pre_signing_validator();
|
||||
let xml = crate::xml_canonical::emit_create_subscription_request_xml(
|
||||
&pre_signing,
|
||||
max_queue_size,
|
||||
sample_interval,
|
||||
);
|
||||
let body = build_create_subscription_request_body(max_queue_size, sample_interval);
|
||||
let response = self
|
||||
.send_signed_envelope(actions::CREATE_SUBSCRIPTION, body, None, false)
|
||||
.send_signed_envelope(actions::CREATE_SUBSCRIPTION, body, Some(&xml), false)
|
||||
.await?;
|
||||
Ok(decode_create_subscription_response(
|
||||
&response.body_tokens,
|
||||
@@ -616,9 +656,16 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> AsbClient<T> {
|
||||
items: &[MinimalMonitoredItem],
|
||||
require_id: bool,
|
||||
) -> Result<AddMonitoredItemsResponse, ClientError> {
|
||||
let pre_signing = self.pre_signing_validator();
|
||||
let xml = crate::xml_canonical::emit_add_monitored_items_request_xml(
|
||||
&pre_signing,
|
||||
subscription_id,
|
||||
items,
|
||||
require_id,
|
||||
);
|
||||
let body = build_add_monitored_items_request_body(subscription_id, items, require_id);
|
||||
let response = self
|
||||
.send_signed_envelope(actions::ADD_MONITORED_ITEMS, body, None, false)
|
||||
.send_signed_envelope(actions::ADD_MONITORED_ITEMS, body, Some(&xml), false)
|
||||
.await?;
|
||||
Ok(decode_add_monitored_items_response(&response.body_tokens)?)
|
||||
}
|
||||
@@ -627,9 +674,11 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> AsbClient<T> {
|
||||
/// available samples. Typical pattern is to call this in a loop
|
||||
/// with a small `tokio::time::timeout` per call.
|
||||
pub async fn publish(&mut self, subscription_id: i64) -> Result<PublishResponse, ClientError> {
|
||||
let pre_signing = self.pre_signing_validator();
|
||||
let xml = crate::xml_canonical::emit_publish_request_xml(&pre_signing, subscription_id);
|
||||
let body = build_publish_request_body(subscription_id);
|
||||
let response = self
|
||||
.send_signed_envelope(actions::PUBLISH, body, None, false)
|
||||
.send_signed_envelope(actions::PUBLISH, body, Some(&xml), false)
|
||||
.await?;
|
||||
Ok(decode_publish_response(&response.body_tokens)?)
|
||||
}
|
||||
@@ -641,9 +690,14 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> AsbClient<T> {
|
||||
&mut self,
|
||||
subscription_id: i64,
|
||||
) -> Result<DeleteSubscriptionResponse, ClientError> {
|
||||
let pre_signing = self.pre_signing_validator();
|
||||
let xml = crate::xml_canonical::emit_delete_subscription_request_xml(
|
||||
&pre_signing,
|
||||
subscription_id,
|
||||
);
|
||||
let body = build_delete_subscription_request_body(subscription_id);
|
||||
let _ = self
|
||||
.send_signed_envelope(actions::DELETE_SUBSCRIPTION, body, None, false)
|
||||
.send_signed_envelope(actions::DELETE_SUBSCRIPTION, body, Some(&xml), false)
|
||||
.await?;
|
||||
Ok(DeleteSubscriptionResponse)
|
||||
}
|
||||
|
||||
@@ -36,10 +36,15 @@
|
||||
use crate::ConnectionValidator;
|
||||
use crate::contracts::ItemIdentity;
|
||||
use crate::envelope::format_uuid;
|
||||
use crate::operations::{MinimalMonitoredItem, MinimalWriteValue};
|
||||
|
||||
const INVENSYS_NS: &str = "urn:invensys.schemas";
|
||||
const DATA_NS: &str = "http://asb.contracts.data/20111111";
|
||||
const IOM_DATA_NS: &str = "urn:data.data.asb.iom:2";
|
||||
/// Variant's per-type namespace (`[XmlType(Namespace = ...)]` on
|
||||
/// `Variant` per `AsbContracts.cs`). Children of a Variant — Type,
|
||||
/// Length, Payload — get this xmlns redeclaration.
|
||||
const IDATA_DATA_NS: &str = "http://asb.contracts.idata.data/20111111";
|
||||
const XSI_NS: &str = "http://www.w3.org/2001/XMLSchema-instance";
|
||||
const XSD_NS: &str = "http://www.w3.org/2001/XMLSchema";
|
||||
|
||||
@@ -115,6 +120,135 @@ pub fn emit_unregister_items_request_xml(
|
||||
})
|
||||
}
|
||||
|
||||
/// `<ReadRequest>` per `AsbContracts.cs:161-167`. Same shape as
|
||||
/// `RegisterItemsRequest` but without `RequireId` / `RegisterOnly`.
|
||||
pub fn emit_read_request_xml(
|
||||
validator: &ConnectionValidator,
|
||||
items: &[ItemIdentity],
|
||||
) -> Vec<u8> {
|
||||
emit_top("ReadRequest", |s| {
|
||||
emit_validator(s, validator);
|
||||
for item in items {
|
||||
emit_item_identity(s, item);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// `<PublishWriteCompleteRequest>` per `AsbContracts.cs:204-205`.
|
||||
/// Empty body — same shape as `KeepAlive`, just a different wrapper
|
||||
/// element.
|
||||
pub fn emit_publish_write_complete_request_xml(validator: &ConnectionValidator) -> Vec<u8> {
|
||||
emit_top("PublishWriteCompleteRequest", |s| {
|
||||
emit_validator(s, validator);
|
||||
})
|
||||
}
|
||||
|
||||
/// `<CreateSubscriptionRequest>` per `AsbContracts.cs:215-223`.
|
||||
/// `MaxQueueSize` (`long`) + `SampleInterval` (`ulong`) — both stay
|
||||
/// in the parent `urn:invensys.schemas` namespace (no per-element
|
||||
/// xmlns redeclaration; the type doesn't carry `[XmlType(Namespace)]`
|
||||
/// because both fields are primitives).
|
||||
pub fn emit_create_subscription_request_xml(
|
||||
validator: &ConnectionValidator,
|
||||
max_queue_size: i64,
|
||||
sample_interval: u64,
|
||||
) -> Vec<u8> {
|
||||
emit_top("CreateSubscriptionRequest", |s| {
|
||||
emit_validator(s, validator);
|
||||
emit_invensys_text(s, " ", "MaxQueueSize", &max_queue_size.to_string());
|
||||
emit_invensys_text(s, " ", "SampleInterval", &sample_interval.to_string());
|
||||
})
|
||||
}
|
||||
|
||||
/// `<DeleteSubscriptionRequest>` per `AsbContracts.cs:232-237`.
|
||||
/// Single primitive `<SubscriptionId>` long.
|
||||
pub fn emit_delete_subscription_request_xml(
|
||||
validator: &ConnectionValidator,
|
||||
subscription_id: i64,
|
||||
) -> Vec<u8> {
|
||||
emit_top("DeleteSubscriptionRequest", |s| {
|
||||
emit_validator(s, validator);
|
||||
emit_invensys_text(s, " ", "SubscriptionId", &subscription_id.to_string());
|
||||
})
|
||||
}
|
||||
|
||||
/// `<PublishRequest>` per `AsbContracts.cs:287-292`. Same shape as
|
||||
/// `DeleteSubscriptionRequest` (single primitive `<SubscriptionId>`).
|
||||
pub fn emit_publish_request_xml(
|
||||
validator: &ConnectionValidator,
|
||||
subscription_id: i64,
|
||||
) -> Vec<u8> {
|
||||
emit_top("PublishRequest", |s| {
|
||||
emit_validator(s, validator);
|
||||
emit_invensys_text(s, " ", "SubscriptionId", &subscription_id.to_string());
|
||||
})
|
||||
}
|
||||
|
||||
/// `<WriteBasicRequest>` per `AsbContracts.cs:181-194`. `Items[]` +
|
||||
/// `Values[]` (each [`MinimalWriteValue`] inlined as a `<Values>`
|
||||
/// element with Value/Status/Comment children) + `WriteHandle`
|
||||
/// (`uint`).
|
||||
///
|
||||
/// `MinimalWriteValue` only carries the inner `Variant`; the optional
|
||||
/// ArrayElementIndex / HasQT / Timestamp fields are `*Specified`-gated
|
||||
/// and never emit when the consumer-visible value is the default.
|
||||
/// `Status` is fixed at the empty-AsbStatus shape (`<Count>0</Count>`).
|
||||
/// `Comment` is fixed at `<Comment xsi:nil="true">` (None — matches
|
||||
/// the captured fixture and the .NET default for `string? Comment;`).
|
||||
pub fn emit_write_basic_request_xml(
|
||||
validator: &ConnectionValidator,
|
||||
items: &[ItemIdentity],
|
||||
values: &[MinimalWriteValue],
|
||||
write_handle: u32,
|
||||
) -> Vec<u8> {
|
||||
emit_top("WriteBasicRequest", |s| {
|
||||
emit_validator(s, validator);
|
||||
for item in items {
|
||||
emit_item_identity(s, item);
|
||||
}
|
||||
for value in values {
|
||||
emit_write_value(s, value);
|
||||
}
|
||||
emit_invensys_text(s, " ", "WriteHandle", &write_handle.to_string());
|
||||
})
|
||||
}
|
||||
|
||||
/// `<AddMonitoredItemsRequest>` per `AsbContracts.cs:242-254`.
|
||||
/// `SubscriptionId` + `Items[]` (each [`MinimalMonitoredItem`] inlined
|
||||
/// as an `<Items>` element with Item/SampleInterval/ValueDeadband/
|
||||
/// UserData/Buffered children) + `RequireId`.
|
||||
pub fn emit_add_monitored_items_request_xml(
|
||||
validator: &ConnectionValidator,
|
||||
subscription_id: i64,
|
||||
items: &[MinimalMonitoredItem],
|
||||
require_id: bool,
|
||||
) -> Vec<u8> {
|
||||
emit_top("AddMonitoredItemsRequest", |s| {
|
||||
emit_validator(s, validator);
|
||||
emit_invensys_text(s, " ", "SubscriptionId", &subscription_id.to_string());
|
||||
for item in items {
|
||||
emit_monitored_item(s, item);
|
||||
}
|
||||
emit_invensys_bool(s, " ", "RequireId", require_id);
|
||||
})
|
||||
}
|
||||
|
||||
/// `<DeleteMonitoredItemsRequest>` per `AsbContracts.cs:268-277`.
|
||||
/// Same as `AddMonitoredItemsRequest` minus the trailing `RequireId`.
|
||||
pub fn emit_delete_monitored_items_request_xml(
|
||||
validator: &ConnectionValidator,
|
||||
subscription_id: i64,
|
||||
items: &[MinimalMonitoredItem],
|
||||
) -> Vec<u8> {
|
||||
emit_top("DeleteMonitoredItemsRequest", |s| {
|
||||
emit_validator(s, validator);
|
||||
emit_invensys_text(s, " ", "SubscriptionId", &subscription_id.to_string());
|
||||
for item in items {
|
||||
emit_monitored_item(s, item);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ---- internal helpers ----------------------------------------------------
|
||||
|
||||
fn emit_top<F: FnOnce(&mut String)>(class_name: &str, body: F) -> Vec<u8> {
|
||||
@@ -294,6 +428,204 @@ fn emit_invensys_bool(s: &mut String, indent: &str, tag: &str, value: bool) {
|
||||
s.push_str(">\r\n");
|
||||
}
|
||||
|
||||
/// Emit a text-bearing element in the default invensys namespace
|
||||
/// (no xmlns redeclaration). Used for primitive int / long / uint
|
||||
/// fields (`MaxQueueSize`, `SampleInterval`, `SubscriptionId`,
|
||||
/// `WriteHandle`).
|
||||
fn emit_invensys_text(s: &mut String, indent: &str, tag: &str, value: &str) {
|
||||
s.push_str(indent);
|
||||
s.push('<');
|
||||
s.push_str(tag);
|
||||
s.push('>');
|
||||
write_xml_escaped_text(s, value);
|
||||
s.push_str("</");
|
||||
s.push_str(tag);
|
||||
s.push_str(">\r\n");
|
||||
}
|
||||
|
||||
/// Emit a `MinimalWriteValue` as a `<Values>` element with inlined
|
||||
/// Value (Variant) + Status + Comment children. Mirrors the captured
|
||||
/// `--dump-signed-xml WriteBasicRequest` shape:
|
||||
///
|
||||
/// ```xml
|
||||
/// <Values>
|
||||
/// <Value xmlns="urn:data.data.asb.iom:2"> ... variant ... </Value>
|
||||
/// <Status xmlns="urn:data.data.asb.iom:2"><Count>0</Count></Status>
|
||||
/// <Comment xsi:nil="true" xmlns="urn:data.data.asb.iom:2" />
|
||||
/// </Values>
|
||||
/// ```
|
||||
///
|
||||
/// XmlSerializer flattens each `WriteValue` array element into a
|
||||
/// `<Values>` wrapper (per `[XmlElement("Values")]` on
|
||||
/// `WriteValue[]?`), then emits each child field with the
|
||||
/// `WriteValue.[XmlType(Namespace = "urn:data.data.asb.iom:2")]`
|
||||
/// redeclaration on the outermost child of each.
|
||||
fn emit_write_value(s: &mut String, value: &MinimalWriteValue) {
|
||||
s.push_str(" <Values>\r\n");
|
||||
// <Value> — wraps the inner Variant. The Value element itself
|
||||
// gets the iom:2 redeclaration; its children (Type/Length/Payload)
|
||||
// get the idata namespace.
|
||||
s.push_str(" <Value xmlns=\"");
|
||||
s.push_str(IOM_DATA_NS);
|
||||
s.push_str("\">\r\n");
|
||||
emit_idata_variant(
|
||||
s,
|
||||
" ",
|
||||
value.value.type_id,
|
||||
value.value.length,
|
||||
&value.value.payload,
|
||||
);
|
||||
s.push_str(" </Value>\r\n");
|
||||
// <Status xmlns="iom:2"><Count>0</Count></Status>. AsbStatus's
|
||||
// field doesn't carry [XmlType(Namespace)], so Count inherits
|
||||
// the parent iom:2 redeclaration that the wrapper added.
|
||||
s.push_str(" <Status xmlns=\"");
|
||||
s.push_str(IOM_DATA_NS);
|
||||
s.push_str("\">\r\n <Count>0</Count>\r\n </Status>\r\n");
|
||||
// <Comment xsi:nil="true" xmlns="iom:2" /> — Comment is
|
||||
// [XmlElement(IsNullable = true)] string?; default-null serialises
|
||||
// as the xsi:nil + xmlns redeclaration form.
|
||||
s.push_str(" <Comment xsi:nil=\"true\" xmlns=\"");
|
||||
s.push_str(IOM_DATA_NS);
|
||||
s.push_str("\" />\r\n");
|
||||
s.push_str(" </Values>\r\n");
|
||||
}
|
||||
|
||||
/// Emit a `MinimalMonitoredItem` as an `<Items>` element with inlined
|
||||
/// Item / SampleInterval / ValueDeadband / UserData / Buffered
|
||||
/// children. Mirrors the `--dump-signed-xml AddMonitoredItemsRequest`
|
||||
/// fixture.
|
||||
///
|
||||
/// Fields not on `MinimalMonitoredItem` (Active / TimeDeadband) are
|
||||
/// `*Specified`-gated and never emit when unset. `ValueDeadband` and
|
||||
/// `UserData` always emit because they are non-nullable `Variant`
|
||||
/// structs — XmlSerializer always serialises them with their default
|
||||
/// `Type=0 Length=0 Payload=nil` shape.
|
||||
fn emit_monitored_item(s: &mut String, item: &MinimalMonitoredItem) {
|
||||
s.push_str(" <Items>\r\n");
|
||||
// <Item xmlns="iom:2"> ... ItemIdentity children, no per-child
|
||||
// xmlns redeclaration since they're already in iom:2.
|
||||
emit_inline_item_identity(s, " ", "Item", &item.item);
|
||||
emit_iom_text(s, " ", "SampleInterval", &item.sample_interval.to_string());
|
||||
// ValueDeadband + UserData: default-Variant shape (type=0,
|
||||
// length=0, payload nil).
|
||||
emit_iom_default_variant(s, " ", "ValueDeadband");
|
||||
emit_iom_default_variant(s, " ", "UserData");
|
||||
emit_iom_text(s, " ", "Buffered", if item.buffered { "true" } else { "false" });
|
||||
s.push_str(" </Items>\r\n");
|
||||
}
|
||||
|
||||
/// Emit an `ItemIdentity` *as a child element of a MonitoredItem* —
|
||||
/// the wrapper carries the iom:2 namespace redeclaration once, and
|
||||
/// children (Type/ReferenceType/Name/ContextName/Id) inherit. Differs
|
||||
/// from [`emit_item_identity`] which is the top-level form where each
|
||||
/// child gets its own redeclaration.
|
||||
fn emit_inline_item_identity(s: &mut String, indent: &str, wrapper: &str, item: &ItemIdentity) {
|
||||
s.push_str(indent);
|
||||
s.push('<');
|
||||
s.push_str(wrapper);
|
||||
s.push_str(" xmlns=\"");
|
||||
s.push_str(IOM_DATA_NS);
|
||||
s.push_str("\">\r\n");
|
||||
let inner_indent = format!("{indent} ");
|
||||
emit_inline_text(s, &inner_indent, "Type", &item.kind.to_string());
|
||||
emit_inline_text(s, &inner_indent, "ReferenceType", &item.reference_type.to_string());
|
||||
emit_inline_optional_string(s, &inner_indent, "Name", item.name.as_deref());
|
||||
emit_inline_optional_string(s, &inner_indent, "ContextName", item.context_name.as_deref());
|
||||
if item.id_specified {
|
||||
emit_inline_text(s, &inner_indent, "Id", &item.id.to_string());
|
||||
}
|
||||
s.push_str(indent);
|
||||
s.push_str("</");
|
||||
s.push_str(wrapper);
|
||||
s.push_str(">\r\n");
|
||||
}
|
||||
|
||||
/// Inline text element — no xmlns redeclaration (consumer is already
|
||||
/// inside an xmlns-scoped wrapper).
|
||||
fn emit_inline_text(s: &mut String, indent: &str, tag: &str, value: &str) {
|
||||
s.push_str(indent);
|
||||
s.push('<');
|
||||
s.push_str(tag);
|
||||
s.push('>');
|
||||
write_xml_escaped_text(s, value);
|
||||
s.push_str("</");
|
||||
s.push_str(tag);
|
||||
s.push_str(">\r\n");
|
||||
}
|
||||
|
||||
/// Inline `[XmlElement(IsNullable = true)]` string. None →
|
||||
/// `<Tag xsi:nil="true" />`; Some("") → `<Tag />`; Some(s) →
|
||||
/// `<Tag>s</Tag>`. Differs from [`emit_iom_optional_string`] in
|
||||
/// omitting the xmlns redeclaration.
|
||||
fn emit_inline_optional_string(s: &mut String, indent: &str, tag: &str, value: Option<&str>) {
|
||||
s.push_str(indent);
|
||||
s.push('<');
|
||||
s.push_str(tag);
|
||||
match value {
|
||||
None => s.push_str(" xsi:nil=\"true\" />\r\n"),
|
||||
Some("") => s.push_str(" />\r\n"),
|
||||
Some(text) => {
|
||||
s.push('>');
|
||||
write_xml_escaped_text(s, text);
|
||||
s.push_str("</");
|
||||
s.push_str(tag);
|
||||
s.push_str(">\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a Variant's children — `<Type>`, `<Length>`, `<Payload>` —
|
||||
/// each carrying the `IDATA_DATA_NS` redeclaration (since
|
||||
/// `Variant.[XmlType(Namespace)]` is `http://asb.contracts.idata.data/20111111`).
|
||||
/// `length == 0` collapses Payload to `<Payload xsi:nil="true" xmlns="..." />`
|
||||
/// matching the captured shape for default-Variant fields.
|
||||
fn emit_idata_variant(s: &mut String, indent: &str, type_id: u16, length: i32, payload: &[u8]) {
|
||||
s.push_str(indent);
|
||||
s.push_str("<Type xmlns=\"");
|
||||
s.push_str(IDATA_DATA_NS);
|
||||
s.push_str("\">");
|
||||
s.push_str(&type_id.to_string());
|
||||
s.push_str("</Type>\r\n");
|
||||
s.push_str(indent);
|
||||
s.push_str("<Length xmlns=\"");
|
||||
s.push_str(IDATA_DATA_NS);
|
||||
s.push_str("\">");
|
||||
s.push_str(&length.to_string());
|
||||
s.push_str("</Length>\r\n");
|
||||
if length == 0 {
|
||||
s.push_str(indent);
|
||||
s.push_str("<Payload xsi:nil=\"true\" xmlns=\"");
|
||||
s.push_str(IDATA_DATA_NS);
|
||||
s.push_str("\" />\r\n");
|
||||
} else {
|
||||
s.push_str(indent);
|
||||
s.push_str("<Payload xmlns=\"");
|
||||
s.push_str(IDATA_DATA_NS);
|
||||
s.push_str("\">");
|
||||
s.push_str(&base64_encode(payload));
|
||||
s.push_str("</Payload>\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a default-shape Variant wrapper (`<Tag xmlns="iom:2">` with
|
||||
/// Type=0 Length=0 Payload-nil children). Used for `ValueDeadband` /
|
||||
/// `UserData` inside MonitoredItem.
|
||||
fn emit_iom_default_variant(s: &mut String, indent: &str, tag: &str) {
|
||||
s.push_str(indent);
|
||||
s.push('<');
|
||||
s.push_str(tag);
|
||||
s.push_str(" xmlns=\"");
|
||||
s.push_str(IOM_DATA_NS);
|
||||
s.push_str("\">\r\n");
|
||||
let inner_indent = format!("{indent} ");
|
||||
emit_idata_variant(s, &inner_indent, 0, 0, &[]);
|
||||
s.push_str(indent);
|
||||
s.push_str("</");
|
||||
s.push_str(tag);
|
||||
s.push_str(">\r\n");
|
||||
}
|
||||
|
||||
/// XML-escape characters that XmlSerializer escapes in text nodes.
|
||||
/// Only `<`, `>`, and `&` are emitted as entities by the .NET writer;
|
||||
/// quotes appear inside attribute values which we control directly,
|
||||
@@ -474,6 +806,104 @@ mod tests {
|
||||
assert_eq_bytes("unregister-items", &actual, &expected);
|
||||
}
|
||||
|
||||
fn pinned_sample_item() -> ItemIdentity {
|
||||
ItemIdentity {
|
||||
kind: 0,
|
||||
reference_type: 1,
|
||||
name: Some("TestChildObject.TestInt".to_string()),
|
||||
context_name: Some(String::new()),
|
||||
id: 0,
|
||||
id_specified: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn pinned_sample_item_by_id() -> ItemIdentity {
|
||||
ItemIdentity {
|
||||
kind: 1,
|
||||
reference_type: 1,
|
||||
name: None,
|
||||
context_name: None,
|
||||
id: 0xCAFE_BABE_DEAD_BEEFu64,
|
||||
id_specified: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// `0x1234_5678_9abc_def0` — same `SampleSubscriptionId` the .NET
|
||||
/// probe pins. Decimal 1311768467463790320.
|
||||
const PINNED_SUB_ID: i64 = 0x1234_5678_9abc_def0;
|
||||
|
||||
#[test]
|
||||
fn read_request_matches_dotnet_fixture() {
|
||||
let validator = pinned_validator();
|
||||
let item = pinned_sample_item();
|
||||
let actual = emit_read_request_xml(&validator, &[item]);
|
||||
let expected = fixture("read-request.xml");
|
||||
assert_eq_bytes("read-request", &actual, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn publish_write_complete_request_matches_dotnet_fixture() {
|
||||
let validator = pinned_validator();
|
||||
let actual = emit_publish_write_complete_request_xml(&validator);
|
||||
let expected = fixture("publish-write-complete-request.xml");
|
||||
assert_eq_bytes("publish-write-complete-request", &actual, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_subscription_request_matches_dotnet_fixture() {
|
||||
let validator = pinned_validator();
|
||||
let actual = emit_create_subscription_request_xml(&validator, 100, 1000);
|
||||
let expected = fixture("create-subscription-request.xml");
|
||||
assert_eq_bytes("create-subscription-request", &actual, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_subscription_request_matches_dotnet_fixture() {
|
||||
let validator = pinned_validator();
|
||||
let actual = emit_delete_subscription_request_xml(&validator, PINNED_SUB_ID);
|
||||
let expected = fixture("delete-subscription-request.xml");
|
||||
assert_eq_bytes("delete-subscription-request", &actual, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn publish_request_matches_dotnet_fixture() {
|
||||
let validator = pinned_validator();
|
||||
let actual = emit_publish_request_xml(&validator, PINNED_SUB_ID);
|
||||
let expected = fixture("publish-request.xml");
|
||||
assert_eq_bytes("publish-request", &actual, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_basic_request_matches_dotnet_fixture() {
|
||||
use mxaccess_codec::AsbVariant;
|
||||
let validator = pinned_validator();
|
||||
let item = pinned_sample_item();
|
||||
let value = MinimalWriteValue::new(AsbVariant::from_i32(42));
|
||||
let actual = emit_write_basic_request_xml(&validator, &[item], &[value], 0xDEAD_BEEFu32);
|
||||
let expected = fixture("write-basic-request.xml");
|
||||
assert_eq_bytes("write-basic-request", &actual, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_monitored_items_request_matches_dotnet_fixture() {
|
||||
let validator = pinned_validator();
|
||||
let item = MinimalMonitoredItem::new(pinned_sample_item(), 1000);
|
||||
let actual =
|
||||
emit_add_monitored_items_request_xml(&validator, PINNED_SUB_ID, &[item], true);
|
||||
let expected = fixture("add-monitored-items-request.xml");
|
||||
assert_eq_bytes("add-monitored-items-request", &actual, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_monitored_items_request_matches_dotnet_fixture() {
|
||||
let validator = pinned_validator();
|
||||
let item = MinimalMonitoredItem::new(pinned_sample_item_by_id(), 1000);
|
||||
let actual =
|
||||
emit_delete_monitored_items_request_xml(&validator, PINNED_SUB_ID, &[item]);
|
||||
let expected = fixture("delete-monitored-items-request.xml");
|
||||
assert_eq_bytes("delete-monitored-items-request", &actual, &expected);
|
||||
}
|
||||
|
||||
/// XML escaping: feed a name with `<` and `&` and confirm the
|
||||
/// emitter produces `<` and `&`. Real wire never carries
|
||||
/// these characters in tag names, but this protects against future
|
||||
|
||||
@@ -89,11 +89,40 @@ source for `XmlSerializer`:
|
||||
## Files
|
||||
|
||||
- `authenticate-me.xml` — `AuthenticateMe`
|
||||
- `authenticate-me-empty-mac-iv.xml` — `AuthenticateMe` with the
|
||||
pre-signing validator (empty MAC + IV) — the actual HMAC input shape.
|
||||
- `disconnect.xml` — `Disconnect`
|
||||
- `keep-alive.xml` — `KeepAlive`
|
||||
- `register-items.xml` — `RegisterItemsRequest`
|
||||
- `unregister-items.xml` — `UnregisterItemsRequest`
|
||||
|
||||
The eight remaining `ConnectedRequest` shapes added 2026-05-06 (F28
|
||||
step 2) cover the data-plane + subscription ops:
|
||||
|
||||
- `read-request.xml` — `ReadRequest`
|
||||
- `write-basic-request.xml` — `WriteBasicRequest`
|
||||
- `publish-write-complete-request.xml` — `PublishWriteCompleteRequest`
|
||||
- `create-subscription-request.xml` — `CreateSubscriptionRequest`
|
||||
- `delete-subscription-request.xml` — `DeleteSubscriptionRequest`
|
||||
- `add-monitored-items-request.xml` — `AddMonitoredItemsRequest`
|
||||
- `delete-monitored-items-request.xml` — `DeleteMonitoredItemsRequest`
|
||||
- `publish-request.xml` — `PublishRequest`
|
||||
|
||||
Pinned values for the new shapes (in addition to the
|
||||
`ConnectionValidator` above):
|
||||
|
||||
- `SubscriptionId = 0x1234_5678_9abc_def0` (decimal `1311768467463790320`)
|
||||
- `MaxQueueSize = 100`, `SampleInterval = 1000`
|
||||
- `WriteHandle = 0xDEAD_BEEF` (decimal `3735928559`)
|
||||
- `WriteBasicRequest` uses one `WriteValue` whose `Value` is
|
||||
`Variant.FromInt32(42)` (`Type=4`, `Length=4`, `Payload=[42, 0, 0, 0]`)
|
||||
- `AddMonitoredItemsRequest` uses one `MonitoredItem` with
|
||||
`Item = "TestChildObject.TestInt"` by name + `SampleInterval=1000` +
|
||||
`Buffered=false` (other fields default)
|
||||
- `DeleteMonitoredItemsRequest` uses one `MonitoredItem` with
|
||||
`Item.Id = 0xCAFE_BABE_DEAD_BEEF` (the same `IdSpecified` shape as
|
||||
`unregister-items.xml`)
|
||||
|
||||
Each file is the verbatim UTF-8 representation of `request.ToXml()`,
|
||||
with literal `\r\n` line endings preserved. Treat as binary (don't
|
||||
let your editor reformat).
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-16"?>
|
||||
<AddMonitoredItemsRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:invensys.schemas">
|
||||
<ConnectionValidator>
|
||||
<ConnectionId xmlns="http://asb.contracts.data/20111111">8cba964a-74c1-ef74-f6aa-761b3540191b</ConnectionId>
|
||||
<MessageNumber xmlns="http://asb.contracts.data/20111111">42</MessageNumber>
|
||||
<MessageAuthenticationCode xmlns="http://asb.contracts.data/20111111">AAECAwQFBgcICQoLDA0ODw==</MessageAuthenticationCode>
|
||||
<SignatureInitializationVector xmlns="http://asb.contracts.data/20111111">EBESExQVFhcYGRobHB0eHw==</SignatureInitializationVector>
|
||||
</ConnectionValidator>
|
||||
<SubscriptionId>1311768467463790320</SubscriptionId>
|
||||
<Items>
|
||||
<Item xmlns="urn:data.data.asb.iom:2">
|
||||
<Type>0</Type>
|
||||
<ReferenceType>1</ReferenceType>
|
||||
<Name>TestChildObject.TestInt</Name>
|
||||
<ContextName />
|
||||
</Item>
|
||||
<SampleInterval xmlns="urn:data.data.asb.iom:2">1000</SampleInterval>
|
||||
<ValueDeadband xmlns="urn:data.data.asb.iom:2">
|
||||
<Type xmlns="http://asb.contracts.idata.data/20111111">0</Type>
|
||||
<Length xmlns="http://asb.contracts.idata.data/20111111">0</Length>
|
||||
<Payload xsi:nil="true" xmlns="http://asb.contracts.idata.data/20111111" />
|
||||
</ValueDeadband>
|
||||
<UserData xmlns="urn:data.data.asb.iom:2">
|
||||
<Type xmlns="http://asb.contracts.idata.data/20111111">0</Type>
|
||||
<Length xmlns="http://asb.contracts.idata.data/20111111">0</Length>
|
||||
<Payload xsi:nil="true" xmlns="http://asb.contracts.idata.data/20111111" />
|
||||
</UserData>
|
||||
<Buffered xmlns="urn:data.data.asb.iom:2">false</Buffered>
|
||||
</Items>
|
||||
<RequireId>true</RequireId>
|
||||
</AddMonitoredItemsRequest>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-16"?>
|
||||
<CreateSubscriptionRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:invensys.schemas">
|
||||
<ConnectionValidator>
|
||||
<ConnectionId xmlns="http://asb.contracts.data/20111111">8cba964a-74c1-ef74-f6aa-761b3540191b</ConnectionId>
|
||||
<MessageNumber xmlns="http://asb.contracts.data/20111111">42</MessageNumber>
|
||||
<MessageAuthenticationCode xmlns="http://asb.contracts.data/20111111">AAECAwQFBgcICQoLDA0ODw==</MessageAuthenticationCode>
|
||||
<SignatureInitializationVector xmlns="http://asb.contracts.data/20111111">EBESExQVFhcYGRobHB0eHw==</SignatureInitializationVector>
|
||||
</ConnectionValidator>
|
||||
<MaxQueueSize>100</MaxQueueSize>
|
||||
<SampleInterval>1000</SampleInterval>
|
||||
</CreateSubscriptionRequest>
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-16"?>
|
||||
<DeleteMonitoredItemsRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:invensys.schemas">
|
||||
<ConnectionValidator>
|
||||
<ConnectionId xmlns="http://asb.contracts.data/20111111">8cba964a-74c1-ef74-f6aa-761b3540191b</ConnectionId>
|
||||
<MessageNumber xmlns="http://asb.contracts.data/20111111">42</MessageNumber>
|
||||
<MessageAuthenticationCode xmlns="http://asb.contracts.data/20111111">AAECAwQFBgcICQoLDA0ODw==</MessageAuthenticationCode>
|
||||
<SignatureInitializationVector xmlns="http://asb.contracts.data/20111111">EBESExQVFhcYGRobHB0eHw==</SignatureInitializationVector>
|
||||
</ConnectionValidator>
|
||||
<SubscriptionId>1311768467463790320</SubscriptionId>
|
||||
<Items>
|
||||
<Item xmlns="urn:data.data.asb.iom:2">
|
||||
<Type>1</Type>
|
||||
<ReferenceType>1</ReferenceType>
|
||||
<Name xsi:nil="true" />
|
||||
<ContextName xsi:nil="true" />
|
||||
<Id>14627333968688430831</Id>
|
||||
</Item>
|
||||
<SampleInterval xmlns="urn:data.data.asb.iom:2">1000</SampleInterval>
|
||||
<ValueDeadband xmlns="urn:data.data.asb.iom:2">
|
||||
<Type xmlns="http://asb.contracts.idata.data/20111111">0</Type>
|
||||
<Length xmlns="http://asb.contracts.idata.data/20111111">0</Length>
|
||||
<Payload xsi:nil="true" xmlns="http://asb.contracts.idata.data/20111111" />
|
||||
</ValueDeadband>
|
||||
<UserData xmlns="urn:data.data.asb.iom:2">
|
||||
<Type xmlns="http://asb.contracts.idata.data/20111111">0</Type>
|
||||
<Length xmlns="http://asb.contracts.idata.data/20111111">0</Length>
|
||||
<Payload xsi:nil="true" xmlns="http://asb.contracts.idata.data/20111111" />
|
||||
</UserData>
|
||||
<Buffered xmlns="urn:data.data.asb.iom:2">false</Buffered>
|
||||
</Items>
|
||||
</DeleteMonitoredItemsRequest>
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-16"?>
|
||||
<DeleteSubscriptionRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:invensys.schemas">
|
||||
<ConnectionValidator>
|
||||
<ConnectionId xmlns="http://asb.contracts.data/20111111">8cba964a-74c1-ef74-f6aa-761b3540191b</ConnectionId>
|
||||
<MessageNumber xmlns="http://asb.contracts.data/20111111">42</MessageNumber>
|
||||
<MessageAuthenticationCode xmlns="http://asb.contracts.data/20111111">AAECAwQFBgcICQoLDA0ODw==</MessageAuthenticationCode>
|
||||
<SignatureInitializationVector xmlns="http://asb.contracts.data/20111111">EBESExQVFhcYGRobHB0eHw==</SignatureInitializationVector>
|
||||
</ConnectionValidator>
|
||||
<SubscriptionId>1311768467463790320</SubscriptionId>
|
||||
</DeleteSubscriptionRequest>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-16"?>
|
||||
<PublishRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:invensys.schemas">
|
||||
<ConnectionValidator>
|
||||
<ConnectionId xmlns="http://asb.contracts.data/20111111">8cba964a-74c1-ef74-f6aa-761b3540191b</ConnectionId>
|
||||
<MessageNumber xmlns="http://asb.contracts.data/20111111">42</MessageNumber>
|
||||
<MessageAuthenticationCode xmlns="http://asb.contracts.data/20111111">AAECAwQFBgcICQoLDA0ODw==</MessageAuthenticationCode>
|
||||
<SignatureInitializationVector xmlns="http://asb.contracts.data/20111111">EBESExQVFhcYGRobHB0eHw==</SignatureInitializationVector>
|
||||
</ConnectionValidator>
|
||||
<SubscriptionId>1311768467463790320</SubscriptionId>
|
||||
</PublishRequest>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-16"?>
|
||||
<PublishWriteCompleteRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:invensys.schemas">
|
||||
<ConnectionValidator>
|
||||
<ConnectionId xmlns="http://asb.contracts.data/20111111">8cba964a-74c1-ef74-f6aa-761b3540191b</ConnectionId>
|
||||
<MessageNumber xmlns="http://asb.contracts.data/20111111">42</MessageNumber>
|
||||
<MessageAuthenticationCode xmlns="http://asb.contracts.data/20111111">AAECAwQFBgcICQoLDA0ODw==</MessageAuthenticationCode>
|
||||
<SignatureInitializationVector xmlns="http://asb.contracts.data/20111111">EBESExQVFhcYGRobHB0eHw==</SignatureInitializationVector>
|
||||
</ConnectionValidator>
|
||||
</PublishWriteCompleteRequest>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-16"?>
|
||||
<ReadRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:invensys.schemas">
|
||||
<ConnectionValidator>
|
||||
<ConnectionId xmlns="http://asb.contracts.data/20111111">8cba964a-74c1-ef74-f6aa-761b3540191b</ConnectionId>
|
||||
<MessageNumber xmlns="http://asb.contracts.data/20111111">42</MessageNumber>
|
||||
<MessageAuthenticationCode xmlns="http://asb.contracts.data/20111111">AAECAwQFBgcICQoLDA0ODw==</MessageAuthenticationCode>
|
||||
<SignatureInitializationVector xmlns="http://asb.contracts.data/20111111">EBESExQVFhcYGRobHB0eHw==</SignatureInitializationVector>
|
||||
</ConnectionValidator>
|
||||
<Items>
|
||||
<Type xmlns="urn:data.data.asb.iom:2">0</Type>
|
||||
<ReferenceType xmlns="urn:data.data.asb.iom:2">1</ReferenceType>
|
||||
<Name xmlns="urn:data.data.asb.iom:2">TestChildObject.TestInt</Name>
|
||||
<ContextName xmlns="urn:data.data.asb.iom:2" />
|
||||
</Items>
|
||||
</ReadRequest>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-16"?>
|
||||
<WriteBasicRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:invensys.schemas">
|
||||
<ConnectionValidator>
|
||||
<ConnectionId xmlns="http://asb.contracts.data/20111111">8cba964a-74c1-ef74-f6aa-761b3540191b</ConnectionId>
|
||||
<MessageNumber xmlns="http://asb.contracts.data/20111111">42</MessageNumber>
|
||||
<MessageAuthenticationCode xmlns="http://asb.contracts.data/20111111">AAECAwQFBgcICQoLDA0ODw==</MessageAuthenticationCode>
|
||||
<SignatureInitializationVector xmlns="http://asb.contracts.data/20111111">EBESExQVFhcYGRobHB0eHw==</SignatureInitializationVector>
|
||||
</ConnectionValidator>
|
||||
<Items>
|
||||
<Type xmlns="urn:data.data.asb.iom:2">0</Type>
|
||||
<ReferenceType xmlns="urn:data.data.asb.iom:2">1</ReferenceType>
|
||||
<Name xmlns="urn:data.data.asb.iom:2">TestChildObject.TestInt</Name>
|
||||
<ContextName xmlns="urn:data.data.asb.iom:2" />
|
||||
</Items>
|
||||
<Values>
|
||||
<Value xmlns="urn:data.data.asb.iom:2">
|
||||
<Type xmlns="http://asb.contracts.idata.data/20111111">4</Type>
|
||||
<Length xmlns="http://asb.contracts.idata.data/20111111">4</Length>
|
||||
<Payload xmlns="http://asb.contracts.idata.data/20111111">KgAAAA==</Payload>
|
||||
</Value>
|
||||
<Status xmlns="urn:data.data.asb.iom:2">
|
||||
<Count>0</Count>
|
||||
</Status>
|
||||
<Comment xsi:nil="true" xmlns="urn:data.data.asb.iom:2" />
|
||||
</Values>
|
||||
<WriteHandle>3735928559</WriteHandle>
|
||||
</WriteBasicRequest>
|
||||
Reference in New Issue
Block a user