test(ab-transport): non-zero array/BOOL captures, committed capture client, sim quirks
Review round 1 on the golden frames: - Re-captured logix-read-dint-array16 (element i = ((i+1)<<24)|(i+1)) and logix-read-bool (= 1) so neither decode oracle is all zeros. ab_server rejects libplctag's own BOOL write with CIP 0x03 (two data bytes for a one-byte BOOL), so the BOOL is seeded by a raw UCMM Write Tag (client seed-bool). - Committed the capture client as scripts/ab-capture/client (outside the slnx, CPM off, libplctag 1.5.2 pinned); capture.sh gains a `client` subcommand. - README documents simulator-only behaviour: zeroed reply sender context, STRING typed D0 00 instead of A0 02 + handle, and the BOOL write refusal. - capture.sh no longer claims UnregisterSession reaches the wire (session ends in RST). - CaptureFileTests also check the CPF walk ends at the frame end and that every reply service is its request service | 0x80.
This commit is contained in:
@@ -20,12 +20,17 @@
|
||||
# Usage:
|
||||
# CAPDIR=<fresh empty dir> scripts/ab-capture/capture.sh run <container> <name> -- <client command...>
|
||||
# scripts/ab-capture/capture.sh tohex <pcap> <out.hex>
|
||||
# scripts/ab-capture/capture.sh client <client args...>
|
||||
#
|
||||
# The client command is whatever drives libplctag; the session used for the
|
||||
# committed fixtures is a ~60-line console program over libplctag 1.5.2 (one tag,
|
||||
# one Initialize + Read/Write, then Dispose + plc_tag_shutdown so the Forward Close
|
||||
# and Unregister Session reach the wire). See Captures/README.md for every
|
||||
# invocation.
|
||||
# `client` builds (once) and runs scripts/ab-capture/client — the console program
|
||||
# over libplctag 1.5.2 that took the committed fixtures: one tag, one Initialize +
|
||||
# Read/Write, then Dispose + plc_tag_shutdown so the Forward Close reaches the wire.
|
||||
# No UnregisterSession is ever sent; the session ends with a TCP RST. Example:
|
||||
#
|
||||
# CAPDIR=$PWD/caps scripts/ab-capture/capture.sh run otopcua-ab-server-controllogix logix-read-dint -- \
|
||||
# scripts/ab-capture/capture.sh client read 10.100.0.35:44818 1,0 ControlLogix TestDINT dint
|
||||
#
|
||||
# See Captures/README.md for every invocation used.
|
||||
#
|
||||
# Fixtures (one AbCip profile at a time — they all bind host :44818):
|
||||
# ssh dohertj2@10.100.0.35 'cd /opt/otopcua-abcip && docker compose --profile controllogix up -d'
|
||||
@@ -39,6 +44,13 @@ REMOTE_DIR="${REMOTE_DIR:-/tmp/abcap}"
|
||||
cmd="${1:-}"; shift || true
|
||||
|
||||
case "$cmd" in
|
||||
client)
|
||||
here="$(cd "$(dirname "$0")" && pwd)"
|
||||
dll="$here/client/bin/Release/net10.0/ab-capture-client.dll"
|
||||
[[ -f "$dll" ]] || dotnet build "$here/client/ab-capture-client.csproj" -c Release -v q >&2
|
||||
exec dotnet "$dll" "$@"
|
||||
;;
|
||||
|
||||
run)
|
||||
container="$1"; name="$2"; shift 2
|
||||
[[ "${1:-}" == "--" ]] && shift
|
||||
@@ -92,5 +104,5 @@ for k, rest in bufs.items():
|
||||
;;
|
||||
|
||||
*)
|
||||
sed -n '2,32p' "$0"; exit 2 ;;
|
||||
sed -n '2,37p' "$0"; exit 2 ;;
|
||||
esac
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
// Capture client for scripts/ab-capture/capture.sh.
|
||||
//
|
||||
// One libplctag tag, one operation, then Dispose + plc_tag_shutdown so the Forward Close
|
||||
// reaches the wire. The tag is built with exactly the properties the drivers set
|
||||
// (LibplctagTagRuntime / LibplctagLegacyTagRuntime / LibplctagTagEnumerator): Gateway, Path,
|
||||
// PlcType, Protocol=ab_eip, Name, Timeout, and ElementCount when > 1. Nothing else.
|
||||
//
|
||||
// usage:
|
||||
// ab-capture-client version
|
||||
// ab-capture-client read <host:port> <path|-> <plc> <name> <type> [count]
|
||||
// ab-capture-client write <host:port> <path|-> <plc> <name> <type> <count> <value>
|
||||
//
|
||||
// <plc> a libplctag.PlcType name: ControlLogix, Micro800, Slc500, MicroLogix, Plc5, LogixPccc
|
||||
// <type> dint | int | real | string | bool | raw (read only: hex dump of the buffer)
|
||||
// dintseq (write only): element i (0-based) = ((i+1) << 24) | (i+1), for <count> elements;
|
||||
// <value> is ignored but required.
|
||||
// env UNCONNECTED=1 sets UseConnectedMessaging=false (ignored by libplctag 2.6.0 for Micro800).
|
||||
using libplctag;
|
||||
using libplctag.NativeImport;
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
Console.Error.WriteLine("usage: see the header of Program.cs");
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (args[0] == "seed-bool")
|
||||
{
|
||||
// ab_server rejects libplctag's BOOL write with CIP 0x03: libplctag sends two data bytes for a
|
||||
// one-byte BOOL and ab_server's "too much data" check refuses it. This seeds a BOOL for a
|
||||
// later libplctag READ capture with one raw UCMM Write Tag carrying exactly one data byte.
|
||||
// usage: seed-bool <host> <port> <tag> <0|1> (route is port 1 / slot 0)
|
||||
return await SeedBoolAsync(args[1], int.Parse(args[2]), args[3], args[4] == "1" ? (byte)1 : (byte)0);
|
||||
}
|
||||
|
||||
if (args[0] == "version")
|
||||
{
|
||||
Console.WriteLine($"{plctag.plc_tag_get_int_attribute(0, "version_major", -1)}." +
|
||||
$"{plctag.plc_tag_get_int_attribute(0, "version_minor", -1)}." +
|
||||
$"{plctag.plc_tag_get_int_attribute(0, "version_patch", -1)}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
var op = args[0];
|
||||
var gateway = args[1];
|
||||
var path = args[2] == "-" ? null : args[2];
|
||||
var plc = Enum.Parse<PlcType>(args[3], ignoreCase: true);
|
||||
var name = args[4];
|
||||
var type = args[5];
|
||||
var count = args.Length > 6 ? int.Parse(args[6]) : 1;
|
||||
|
||||
var tag = new Tag
|
||||
{
|
||||
Gateway = gateway,
|
||||
Path = path,
|
||||
PlcType = plc,
|
||||
Protocol = Protocol.ab_eip,
|
||||
Name = name,
|
||||
Timeout = TimeSpan.FromSeconds(5),
|
||||
};
|
||||
if (count > 1) tag.ElementCount = count;
|
||||
if (Environment.GetEnvironmentVariable("UNCONNECTED") == "1") tag.UseConnectedMessaging = false;
|
||||
|
||||
var exit = 0;
|
||||
try
|
||||
{
|
||||
await tag.InitializeAsync();
|
||||
if (op == "write")
|
||||
{
|
||||
var value = args[7];
|
||||
switch (type)
|
||||
{
|
||||
case "dint": tag.SetInt32(0, int.Parse(value)); break;
|
||||
case "int": tag.SetInt16(0, short.Parse(value)); break;
|
||||
case "real": tag.SetFloat32(0, float.Parse(value)); break;
|
||||
case "string": tag.SetString(0, value); break;
|
||||
// As LibplctagTagRuntime does: a whole-byte write (SetBit on a Logix BOOL is ErrorBadParam).
|
||||
case "bool": tag.SetInt8(0, value is "1" or "true" ? (sbyte)1 : (sbyte)0); break;
|
||||
case "dintseq":
|
||||
for (var i = 0; i < count; i++) tag.SetInt32(i * 4, ((i + 1) << 24) | (i + 1));
|
||||
break;
|
||||
default: throw new ArgumentException($"unknown write type {type}");
|
||||
}
|
||||
await tag.WriteAsync();
|
||||
Console.WriteLine("write ok");
|
||||
}
|
||||
else
|
||||
{
|
||||
await tag.ReadAsync();
|
||||
Console.WriteLine($"read ok size={tag.GetSize()}");
|
||||
Console.WriteLine(type switch
|
||||
{
|
||||
"dint" => tag.GetInt32(0).ToString(),
|
||||
"int" => tag.GetInt16(0).ToString(),
|
||||
"real" => tag.GetFloat32(0).ToString(),
|
||||
"string" => tag.GetString(0),
|
||||
"bool" => tag.GetBit(0).ToString(),
|
||||
_ => Convert.ToHexString(tag.GetBuffer()),
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"EXC {ex.GetType().Name}: {ex.Message}");
|
||||
exit = 1;
|
||||
}
|
||||
finally
|
||||
{
|
||||
tag.Dispose();
|
||||
}
|
||||
|
||||
plctag.plc_tag_shutdown();
|
||||
await Task.Delay(500); // let the Forward Close flush before the process exits
|
||||
return exit;
|
||||
|
||||
static async Task<int> SeedBoolAsync(string host, int port, string tagName, byte value)
|
||||
{
|
||||
using var tcp = new System.Net.Sockets.TcpClient();
|
||||
await tcp.ConnectAsync(host, port);
|
||||
var s = tcp.GetStream();
|
||||
|
||||
// RegisterSession
|
||||
var reg = new byte[28];
|
||||
reg[0] = 0x65; reg[2] = 4; reg[24] = 1;
|
||||
await s.WriteAsync(reg);
|
||||
var regReply = await ReadFrameAsync(s);
|
||||
var session = BitConverter.ToUInt32(regReply, 4);
|
||||
|
||||
// Write Tag (0x4D): ANSI symbol segment, type BOOL 0xC1, count 1, ONE data byte.
|
||||
var name = System.Text.Encoding.ASCII.GetBytes(tagName);
|
||||
var sym = new List<byte> { 0x91, (byte)name.Length };
|
||||
sym.AddRange(name);
|
||||
if (sym.Count % 2 == 1) sym.Add(0);
|
||||
var write = new List<byte> { 0x4D, (byte)(sym.Count / 2) };
|
||||
write.AddRange(sym);
|
||||
write.AddRange([0xC1, 0x00, 0x01, 0x00, value]);
|
||||
|
||||
// Unconnected Send (0x52) to the Connection Manager, routed to port 1 / slot 0.
|
||||
var ucmm = new List<byte> { 0x52, 0x02, 0x20, 0x06, 0x24, 0x01, 0x0A, 0x05 };
|
||||
ucmm.AddRange(BitConverter.GetBytes((ushort)write.Count));
|
||||
ucmm.AddRange(write);
|
||||
if (write.Count % 2 == 1) ucmm.Add(0);
|
||||
ucmm.AddRange([0x01, 0x00, 0x01, 0x00]);
|
||||
|
||||
// SendRRData: interface handle, timeout, 2 items (null address, unconnected data).
|
||||
var data = new List<byte> { 0, 0, 0, 0, 0x0A, 0, 2, 0, 0, 0, 0, 0, 0xB2, 0 };
|
||||
data.AddRange(BitConverter.GetBytes((ushort)ucmm.Count));
|
||||
data.AddRange(ucmm);
|
||||
var frame = new byte[24 + data.Count];
|
||||
frame[0] = 0x6F;
|
||||
BitConverter.GetBytes((ushort)data.Count).CopyTo(frame, 2);
|
||||
BitConverter.GetBytes(session).CopyTo(frame, 4);
|
||||
data.CopyTo(frame, 24);
|
||||
await s.WriteAsync(frame);
|
||||
|
||||
var reply = await ReadFrameAsync(s);
|
||||
var cip = reply.AsSpan(24 + 16).ToArray(); // after handle, timeout, count, null item, data item header
|
||||
Console.WriteLine($"seed-bool reply CIP: {Convert.ToHexString(cip)}");
|
||||
return cip.Length >= 3 && cip[0] == 0xCD && cip[2] == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
static async Task<byte[]> ReadFrameAsync(System.Net.Sockets.NetworkStream s)
|
||||
{
|
||||
var header = new byte[24];
|
||||
await s.ReadExactlyAsync(header);
|
||||
var body = new byte[BitConverter.ToUInt16(header, 2)];
|
||||
await s.ReadExactlyAsync(body);
|
||||
return [.. header, .. body];
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!--
|
||||
Capture client for scripts/ab-capture/capture.sh — drives libplctag 1.5.2 against the ab_server
|
||||
fixtures so the golden frames in tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Captures
|
||||
can be re-taken. Deliberately NOT in ZB.MOM.WW.OtOpcUa.slnx. Pinned to the exact libplctag the
|
||||
drivers use; CPM is switched off so the pin lives here and cannot drift with Directory.Packages.props.
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="libplctag" Version="1.5.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
+30
@@ -50,6 +50,36 @@ public sealed class CaptureFileTests
|
||||
for (var i = 0; i < frames.Count; i++) frames[i].FromClient.ShouldBe(i % 2 == 0, $"{name} frame {i}");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AllCaptures))]
|
||||
public void Every_CPF_item_walk_ends_exactly_at_the_frame_end(string name)
|
||||
{
|
||||
foreach (var f in CaptureFile.Load(name).Where(f => BitConverter.ToUInt16(f.Bytes, 0) is 0x6F or 0x70))
|
||||
{
|
||||
var pos = 24 + 4 + 2;
|
||||
var itemCount = BitConverter.ToUInt16(f.Bytes, pos);
|
||||
pos += 2;
|
||||
for (var i = 0; i < itemCount; i++) pos += 4 + BitConverter.ToUInt16(f.Bytes, pos + 2);
|
||||
|
||||
pos.ShouldBe(f.Bytes.Length, name);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AllCaptures))]
|
||||
public void Every_reply_service_is_its_request_service_with_the_reply_bit(string name)
|
||||
{
|
||||
var frames = CaptureFile.Load(name);
|
||||
for (var i = 0; i + 1 < frames.Count; i += 2)
|
||||
{
|
||||
var request = CaptureFile.CipPayload(frames[i]);
|
||||
var reply = CaptureFile.CipPayload(frames[i + 1]);
|
||||
if (request.Length == 0) continue; // RegisterSession: no CIP
|
||||
|
||||
reply[0].ShouldBe((byte)(request[0] | 0x80), $"{name} pair {i / 2}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CipPayload_strips_the_connected_sequence_count()
|
||||
{
|
||||
|
||||
@@ -21,6 +21,27 @@ Values are **little-endian on the wire** and carry live per-session values — s
|
||||
connection IDs, connection serial, PCCC TNS. A parity test must compare structure and the
|
||||
fields it builds, not those session-assigned values.
|
||||
|
||||
### Simulator behaviour that is NOT controller behaviour
|
||||
|
||||
- **ab_server zeroes the reply sender context.** libplctag sends a non-zero 8-byte sender context
|
||||
on every SendRRData request (e.g. `45A0091C00000000` in `logix-forward-open.hex`), and every
|
||||
ab_server reply carries `0000000000000000`. A real target echoes the request's context.
|
||||
Consequences: parity tests must **exclude** the sender-context field (header bytes 12–19), and
|
||||
the native transport **must not depend on the echo** to correlate replies, or it will pass
|
||||
against a real controller and fail against the sim, or the reverse.
|
||||
- **ab_server types a STRING as atomic `D0 00`, not as a structure.** A STRING read reply (see
|
||||
`logix-read-string.hex`) is `D2 00 00 00` + type `D0 00` + 88 data bytes: a 4-byte `LEN`
|
||||
(`07 00 00 00` for `"HelloAB"`), the 82-byte `DATA` field, and 2 pad bytes. Write requests
|
||||
(`logix-write-string.hex`) echo the same `D0 00` type. A real Logix STRING is a structure:
|
||||
type `A0 02` followed by the 2-byte structure handle, then the same 88-byte body. Tasks 7 and 8
|
||||
should treat the `D0 00` type as simulator behaviour. Byte parity on the type field holds only
|
||||
against this sim, and a STRING codec must accept the `A0 02` + handle form from a controller.
|
||||
- **ab_server refuses libplctag's BOOL write.** libplctag sends two data bytes (`01 00`) for a
|
||||
one-byte BOOL, and ab_server's "too much data" check (`cip.c` in `handle_write_request`) answers
|
||||
CIP 0x03. That is why there is no BOOL write fixture. `logix-read-bool.hex` was seeded by a raw
|
||||
UCMM Write Tag carrying one data byte (`client seed-bool`, below). Incidentally, that write shows
|
||||
ab_server accepts an Unconnected Send (0x52) routed `01 00` in ControlLogix mode.
|
||||
|
||||
## How they were taken
|
||||
|
||||
| | |
|
||||
@@ -32,9 +53,10 @@ fields it builds, not those session-assigned values.
|
||||
| AbCip compose | `/opt/otopcua-abcip` on `10.100.0.35` (= `tests/.../AbCip.IntegrationTests/Docker/`), host `:44818` |
|
||||
| AbLegacy compose | `~/otopcua-ablegacy` on `10.100.0.35` (= `tests/.../AbLegacy.IntegrationTests/Docker/`), host `:44819` slc500 / `:44820` micrologix / `:44821` plc5 |
|
||||
| Capture point | `tcpdump -i eth0 tcp port 44818` in a `nicolaka/netshoot:v0.13` sidecar sharing the simulator container's network namespace; `tshark` 4.2.4 in the same image; segments re-joined into frames by the 24-byte header length field |
|
||||
| Recipe | `scripts/ab-capture/capture.sh run …` then `capture.sh tohex …` |
|
||||
| Recipe | `scripts/ab-capture/capture.sh run <container> <name> -- scripts/ab-capture/capture.sh client …`, then `capture.sh tohex …` |
|
||||
| Client | `scripts/ab-capture/client/` (`ab-capture-client.csproj`). Not in the solution; pins libplctag 1.5.2 with CPM off. `capture.sh client …` builds it once and runs it |
|
||||
|
||||
The client was a ~60-line console program over libplctag 1.5.2 that mirrors how
|
||||
The client (`scripts/ab-capture/client/Program.cs`) is a small console program over libplctag 1.5.2 that mirrors how
|
||||
`LibplctagTagRuntime` / `LibplctagLegacyTagRuntime` / `LibplctagTagEnumerator` build a tag
|
||||
(`Gateway`, `Path`, `PlcType`, `Protocol=ab_eip`, `Name`, `Timeout=5s`, `ElementCount` when > 1;
|
||||
no other attributes), then runs `InitializeAsync` + one `ReadAsync` or `WriteAsync`, disposes the
|
||||
@@ -42,6 +64,26 @@ tag and calls `plc_tag_shutdown` so the Forward Close reaches the wire. **One fr
|
||||
capture**, one profile up at a time. Note that `InitializeAsync` itself performs a read, so a
|
||||
"read" capture holds two read exchanges and a "write" capture holds a read then the write.
|
||||
|
||||
Values were seeded before the reads so that no decode oracle is all zeros. Seeding commands
|
||||
(they were not captured):
|
||||
|
||||
```bash
|
||||
C=scripts/ab-capture/capture.sh
|
||||
$C client write 10.100.0.35:44818 1,0 ControlLogix TestDINT dint 1 123456
|
||||
$C client write 10.100.0.35:44818 1,0 ControlLogix TestString string 1 HelloAB
|
||||
$C client write 10.100.0.35:44818 1,0 ControlLogix TestArray dintseq 16 x # element i (0-based) = ((i+1)<<24)|(i+1)
|
||||
$C client seed-bool 10.100.0.35 44818 TestBOOL 1 # raw UCMM, see above
|
||||
```
|
||||
|
||||
The `logix-write-*` captures are themselves the seeds for TestDINT and TestString. Each read
|
||||
capture is then, for example:
|
||||
|
||||
```bash
|
||||
CAPDIR=<fresh dir> $C run otopcua-ab-server-controllogix logix-read-dint-array16 -- \
|
||||
$C client read 10.100.0.35:44818 1,0 ControlLogix TestArray raw 16
|
||||
$C tohex <fresh dir>/logix-read-dint-array16.pcap logix-read-dint-array16.full.hex
|
||||
```
|
||||
|
||||
### Per-file record
|
||||
|
||||
All files: libplctag 1.5.2 (native 2.6.0), image `otopcua-ab-server:libplctag-release`. The
|
||||
@@ -52,9 +94,9 @@ tag string is the libplctag attribute set the wrapper was given.
|
||||
| `logix-register-session.hex` | controllogix | `protocol=ab_eip&gateway=10.100.0.35:44818&path=1,0&plc=controllogix&name=TestDINT` | RegisterSession (frames 1–2 of the `logix-read-dint` session) |
|
||||
| `logix-forward-open.hex` | controllogix | same as above | Large Forward Open **0x5B** (frames 3–4) |
|
||||
| `logix-read-dint.hex` | controllogix | `…&plc=controllogix&name=TestDINT` | 2× connected Read Tag Fragmented 0x52 (value 123456) |
|
||||
| `logix-read-dint-array16.hex` | controllogix | `…&name=TestArray&elem_count=16` | 2× 0x52, 16 elements |
|
||||
| `logix-read-dint-array16.hex` | controllogix | `…&name=TestArray&elem_count=16` | 2× 0x52, 16 elements; element i (0-based) = `((i+1)<<24)\|(i+1)`, i.e. `0x01000001` … `0x10000010` (LE on the wire: `01 00 00 01`, `02 00 00 02` …) |
|
||||
| `logix-read-string.hex` | controllogix | `…&name=TestString` | 2× 0x52, STRING `"HelloAB"` |
|
||||
| `logix-read-bool.hex` | controllogix | `…&name=TestBOOL` | 2× 0x52, BOOL 0 |
|
||||
| `logix-read-bool.hex` | controllogix | `…&name=TestBOOL` | 2× 0x52, BOOL **1** (seeded via `client seed-bool`) |
|
||||
| `logix-write-dint.hex` | controllogix | `…&name=TestDINT` | 0x52 read, then Write Tag 0x4D of 123456 |
|
||||
| `logix-write-string.hex` | controllogix | `…&name=TestString` | 0x52 read, then 0x4D of `"HelloAB"` |
|
||||
| `logix-read-unknown-tag.hex` | controllogix | `…&name=NoSuchTag` | 0x52 → general status **0x04** (patch 0002); libplctag raised `ErrorNotFound`, no retry |
|
||||
@@ -70,7 +112,7 @@ tag string is the libplctag attribute set the wrapper was given.
|
||||
Logix files other than `register-session`/`forward-open`/`forward-close` hold **only the
|
||||
operation's exchanges**; the handshake frames of their sessions are byte-for-byte the same shape
|
||||
as those three files. The Micro800 and PCCC files hold the **whole session** so their routing is
|
||||
visible in one place. No session in any family sent UnregisterSession (0x66).
|
||||
visible in one place. No session in any family sent UnregisterSession (0x66); each ends with a TCP RST after the last reply.
|
||||
|
||||
## Routing observed
|
||||
|
||||
|
||||
+5
-5
@@ -4,8 +4,8 @@
|
||||
# profile: controllogix
|
||||
# tag : protocol=ab_eip&gateway=10.100.0.35:44818&path=1,0&plc=controllogix&name=TestBOOL
|
||||
# session: logix-read-bool (captured 2026-10-07, scripts/ab-capture/capture.sh)
|
||||
# frames : Two connected 0x52 exchanges for a BOOL (Initialize read + explicit Read). TestBOOL held 0.
|
||||
> 7000280074EA6678000000000000000000000000000000000000000001000200A1000400ABDA0000B100140001005205910854657374424F4F4C010000000000
|
||||
< 70001D0074EA6678000000000000000000000000000000000000000001000200A100040039377043B10009000100D2000000C10000
|
||||
> 7000280074EA6678000000000000000000000000000000000000000001000200A1000400ABDA0000B100140002005205910854657374424F4F4C010000000000
|
||||
< 70001D0074EA6678000000000000000000000000000000000000000001000200A100040039377043B10009000200D2000000C10000
|
||||
# frames : Two connected 0x52 exchanges for a BOOL (Initialize read + explicit Read). TestBOOL held 1, seeded by a raw UCMM Write Tag (client seed-bool) because ab_server rejects libplctag's own BOOL write with CIP 0x03.
|
||||
> 70002800F7F16E35000000000000000000000000000000000000000001000200A10004001EEDEAA0B100140001005205910854657374424F4F4C010000000000
|
||||
< 70001D00F7F16E35000000000000000000000000000000000000000001000200A10004004DFBAD17B10009000100D2000000C10001
|
||||
> 70002800F7F16E35000000000000000000000000000000000000000001000200A10004001EEDEAA0B100140002005205910854657374424F4F4C010000000000
|
||||
< 70001D00F7F16E35000000000000000000000000000000000000000001000200A10004004DFBAD17B10009000200D2000000C10001
|
||||
|
||||
+5
-5
@@ -4,8 +4,8 @@
|
||||
# profile: controllogix
|
||||
# tag : protocol=ab_eip&gateway=10.100.0.35:44818&path=1,0&plc=controllogix&name=TestArray&elem_count=16
|
||||
# session: logix-read-dint-array16 (captured 2026-10-07, scripts/ab-capture/capture.sh)
|
||||
# frames : Two connected 0x52 exchanges for 16 DINT elements (Initialize read + explicit Read).
|
||||
> 70002A00C222CFC1000000000000000000000000000000000000000001000200A1000400B0B13F6BB100160001005206910954657374417272617900100000000000
|
||||
< 70005C00C222CFC1000000000000000000000000000000000000000001000200A1000400E59A1D32B10048000100D2000000C40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||
> 70002A00C222CFC1000000000000000000000000000000000000000001000200A1000400B0B13F6BB100160002005206910954657374417272617900100000000000
|
||||
< 70005C00C222CFC1000000000000000000000000000000000000000001000200A1000400E59A1D32B10048000200D2000000C40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||
# frames : Two connected 0x52 exchanges for 16 DINT elements (Initialize read + explicit Read). Seeded first (client write ... dintseq): element i (0-based) = ((i+1) << 24) | (i+1), i.e. 0x01000001, 0x02000002 ... 0x10000010.
|
||||
> 70002A00F43B6DB3000000000000000000000000000000000000000001000200A1000400A07474F5B100160001005206910954657374417272617900100000000000
|
||||
< 70005C00F43B6DB3000000000000000000000000000000000000000001000200A1000400943EDA10B10048000100D2000000C4000100000102000002030000030400000405000005060000060700000708000008090000090A00000A0B00000B0C00000C0D00000D0E00000E0F00000F10000010
|
||||
> 70002A00F43B6DB3000000000000000000000000000000000000000001000200A1000400A07474F5B100160002005206910954657374417272617900100000000000
|
||||
< 70005C00F43B6DB3000000000000000000000000000000000000000001000200A1000400943EDA10B10048000200D2000000C4000100000102000002030000030400000405000005060000060700000708000008090000090A00000A0B00000B0C00000C0D00000D0E00000E0F00000F10000010
|
||||
|
||||
Reference in New Issue
Block a user