Skip to content

v1.2.0

Choose a tag to compare

@sergiogallegos sergiogallegos released this 10 Jul 22:35
· 52 commits to main since this release

rust-ethernet-ip v1.2.0

A behavioral-fix, hardening, and additive-surface release. No breaking changes
to the public Rust API
(verified by cargo-semver-checks: 195/195 checks pass
against the 1.1.0 baseline, across the main crate and all four sibling crates).
MSRV remains Rust 1.96. Upgrading from 1.1.0 is a drop-in version bump for
Rust, C#, and Python consumers.

C FFI ABI note: the ABI version moves to 2 — three unusable
*mut EipClient raw-pointer exports were removed and eip_abi_version() was
bumped. The shipped C#/Python packages are unaffected (they import only the
_by_id exports and move in lockstep with the native library); a hypothetical
third-party direct C consumer fails fast at the version handshake instead of
misbehaving. The new include/rust_ethernet_ip.h header is the supported C/C++
contract.

Validated end-to-end across all four bindings against CompactLogix hardware
(5069-L330ERM fw38): 2304/2304 reads, 2285/2285 writes, 2285/2285 verify,
0 anomalies
— the full-coverage gate now writes and verifies STRING tags,
including STRING members inside UDTs and UDT array elements.

Install

# Rust — Cargo.toml
rust-ethernet-ip = "1.2.0"
<!-- C# -->
<PackageReference Include="RustEtherNetIp" Version="1.2.0" />
# Python
pip install rust-ethernet-ip==1.2.0
/* C/C++ — link the cdylib and include the shipped header */
#include "rust_ethernet_ip.h"

✨ Highlights

  • Custom Logix string types write and read. Studio 5000 users routinely
    define their own string types (own name and length, e.g. Str82, Str400).
    Previous releases hardcoded the built-in STRING structure handle (0x0FCE),
    so writes to custom string types were rejected with CIP 0x2107 and were
    misdocumented as firmware-blocked. The library now discovers the target tag's
    real structure handle and sizes the payload to the target type — the normal
    write_tag / WriteString / write_tag / write_string APIs work for
    built-in and custom string types alike, standalone or inside UDTs and UDT
    array elements, in controller and program scope. New read_string_tag (Rust)
    and Client.read_string (Python) decode any string-shaped structure to text.
    See docs/STRING_HANDLING.md.
  • CIP fragmentation (Read/Write Tag Fragmented, 0x52/0x53). Strings and
    structures larger than one CIP packet (~490-byte request / ~500-byte reply on
    the validated target) are now read by reassembling fragments and written by
    splitting into fragments — including whole large-UDT array-element reads.
  • Packet-size-aware batch grouping. Batch operations now split by both
    operation count and the actual packet-size budget (measured from the real
    Multiple Service Packet + Unconnected Send framing). Fixes large batch reads
    failing wholesale with EtherNet/IP status 0x65 (Invalid Length).
  • First-class C/C++ consumer support. A checked-in C99 header
    (include/rust_ethernet_ip.h, CI-enforced parity with the exported symbol
    table), a CMake RAII example (examples/cpp/), a manifest-driven C++
    full-coverage exerciser, and a Qt integration guide (docs/CPP_INTEGRATION.md).
  • Transport & session hardening. Timeout desynchronization fixes,
    sender-context correlation, shared session handles, and lag-tolerant
    subscription/fleet event forwarding (stop() stops, abandoned consumers can't
    wedge polling).
  • Tag-addressing correctness. Member-suffix preservation, bit-syntax fixes,
    batch BOOL-array handling, .DATA[i] segments, and program-tag discovery.
  • Honest diagnostics. Operation/error counters are real per-client atomics;
    placeholder system metrics are explicitly flagged; never-worked public
    STRING/UDT paths return explicit unsupported errors instead of failing
    mysteriously (deprecated, removal queued for 2.0).

🔧 Fixed

  • STRING wire format: writes emit the documented Logix structure encoding
    (0x02A0 + handle + padded payload); standard STRING reads decode to
    PlcValue::String. The "firmware blocks STRING writes" claim is retired —
    the historical 0x2107 was a structure-handle/type mismatch.
  • Scalar UDT-array-element member writes (e.g. MyUdtArray[0].Speed) write
    directly when the full member path is preserved.
  • read_array_range and get_tag_attributes wire fixes; the in-process
    simulator was made an oracle rather than a mirror of library bugs.
  • C# wrapper: WriteUdtMember deadlock, UTF-8 marshalling, keep-alive
    serialization; native P/Invoke integration tests.
  • UDT crate strictness: truncated member ranges and missing member values now
    fail closed instead of silently skipping/zero-filling.

📦 Release contents

  • crates.io: rust-ethernet-ip 1.2.0 plus sibling crates
    (-types, -tag-path, -protocol, -udt) at 1.2.0.
  • NuGet: RustEtherNetIp 1.2.0 (win-x64 / linux-x64 / osx-arm64).
  • PyPI: rust-ethernet-ip 1.2.0 (Windows / macOS / manylinux wheels + sdist).

Known limits

  • A single string read/write must fit one CIP packet unless the fragmented path
    applies; practical custom-string maximum is ~400–450 bytes of DATA
    depending on tag-path length (Str400 works in every scope on the validated
    target). Details and per-scope table: docs/STRING_HANDLING.md.
  • Whole-UDT array-element writes as a single structure are not supported —
    update members individually. Whole-element reads work (fragmented when
    large).
  • read_tag returns custom string types as PlcValue::Udt; use
    read_string_tag when the tag is known to be a string.

Validation

  • Cross-binding full-coverage hardware run (Rust/C#/Python/C++), CompactLogix
    5069-L330ERM fw38: 2304 reads / 2285 writes / 2285 verify / 0 anomalies, with
    STRING members (built-in and custom types) written and verified.
  • Evidence: docs/validation/2026-07-08_cross-binding_full-coverage_5069-L330ERM_fw38.md.

Full Changelog: v1.1.0...v1.2.0