Skip to content

PowerMACS 4000: multiple Pack() and Parse() bugs — length prefix mismatch, field corruption, NPE, and incomplete Mid0002 parsing #129

Description

@justinzhou0928

OpenProtocolInterpreter 6.1.1 — PowerMACS 4000 Bugs

Environment

  • Library: OpenProtocolInterpreter 6.1.1
  • Controller: PowerMACS 4000 (Station: OP2160)
  • Open Protocol: Revision 1 handshake (MID 0001/0002)
  • Connection: TCP, length-prefix framing

Summary

Five bugs found while building a production gateway service that connects to real PowerMACS 4000 controllers:

  1. Mid0105.Pack() — length prefix larger than actual body, controller hangs
  2. Mid0106.Pack() — length prefix mismatch + field offset corruption, cannot roundtrip
  3. Mid0107.Pack() — NPE when BoltResults is non-empty
  4. Mid0002.Parse() — fails on PowerMACS 4000 minimal format, fields parsed as 0/null
  5. Mid0008 / Mid0009.Pack() — length prefix too large, controller hangs

Bug 1: Mid0105.Pack() — length prefix larger than actual body

var subscribe = new Mid0105(3) { SendOnlyNewData = true };
var packed = subscribe.Pack();
// packed starts with "0236" (declaring 236 bytes)
// but packed.Length == 226 bytes

Impact: The controller reads the declared length prefix and blocks waiting for 10 bytes that never arrive. Connection stalls indefinitely.

Workaround: Bypass PackBytes() entirely; send raw string padded to declared length:

if (msg.Length >= 4 && int.TryParse(msg.AsSpan(0, 4), out var declared) && msg.Length < declared)
    msg = msg.PadRight(declared);

Bug 2: Mid0106.Pack() — length prefix mismatch + field offset corruption

var mid = new Mid0106
{
    DataNumberSystem = 1001,
    StationName = "ST-1",
    ModeName = "Program-A",
    WpId = "ABC-260617110000",
    BoltsData = [new BoltData { OrdinalBoltNumber = 1 }],
};

var packed = mid.Pack();
// Length prefix: "0236", but packed.Length == 226

// Roundtrip failure:
var parsed = interpreter.Parse(packed) as Mid0106;
// parsed.DataNumberSystem == 10010405  ← expected 1001
// parsed.NumberOfBolts == 50           ← expected 1

Impact: The library cannot round-trip its own output. Tests that need to construct valid Mid0106 ASCII frames are impossible.


Bug 3: Mid0107.Pack() — NPE when BoltResults is populated

var mid = new Mid0107
{
    BoltNumber = 1,
    BoltName = "B-1",
    ProgramName = "Program-A",
    BoltResults = new List<BoltResult>
    {
        new() { VariableName = "TORQUE", Value = 12.5m, Type = "F" },
    },
};
var packed = mid.Pack(); // NullReferenceException

Impact: Cannot construct Mid0107 messages containing bolt result variables.


Bug 4: Mid0002.Parse() — fails on PowerMACS 4000 minimal format

PowerMACS 4000 sends a minimal 57-byte Mid0002. Real frame captured from controller:

00570002001         01    02  03OP2160

Field breakdown:

Offset Length Content Field
0 4 0057 Length = 57
4 4 0002 MID = 0002
8 3 001 Revision = 1
11 1 NoAck flag
12 9 Reserved
21 2 01 Station ID
23 4 Reserved
27 2 02 Cell ID
29 3 Reserved
32 2 03 Channel ID
34 23 OP2160 + 19 spaces Controller Name

The frame ends after ControllerName. There is no SupplierCode, OpenProtocolVersion, ControllerSoftwareVersion, ToolSoftwareVersion, SystemType, etc.

Parse results vs actual values:

Property Actual in frame Library Parse result
CellId 02 0
ChannelId 03 0
ControllerName OP2160 OP2160 (correct)
OpenProtocolVersion not present null
ControllerSoftwareVersion not present null
ToolSoftwareVersion not present null
SystemType not present SystemTypeNotSet
SystemSubType not present NoSubtypeExists

Even the fields that ARE present (CellId=02, ChannelId=03) are parsed as 0.

Impact: Cannot determine controller firmware version, protocol revision, or system capabilities from the handshake response.


Bug 5: Mid0008 / Mid0009.Pack() — length prefix too large

Same pattern as Mid0105 — the length prefix exceeds actual output for messages with ExtraData fields.

var sub = new Mid0008
{
    SubscriptionMid = "0900",
    WantedRevision = 1,
    ExtraDataLength = extraData.Length,
    ExtraData = extraData,
};
var packed = sub.Pack();
// packed.Substring(0, 4) declares length X, but packed.Length < X

Workaround:

private static string NormalizeLength(string msg)
{
    if (msg.Length >= 4 && int.TryParse(msg.AsSpan(0, 4), out var declared) && msg.Length < declared)
        return msg.PadRight(declared);
    return msg;
}

CurveSubscriptionMessage = NormalizeLength(BuildCurveSubscription(opt));
CurveUnsubscribeMessage = NormalizeLength(new Mid0009 { ... }.Pack());

Summary table

MID Method Issue
Mid0105 Pack() Length prefix too large (0236 vs 226); controller hangs
Mid0106 Pack() Length prefix mismatch + field offset corruption; cannot roundtrip
Mid0107 Pack() NPE when BoltResults non-empty
Mid0002 Parse() Doesn't handle PowerMACS 4000 minimal format; fields parsed as 0/null
Mid0008 Pack() Length prefix too large; controller hangs
Mid0009 Pack() Length prefix too large; controller hangs

Note: Parse() for Mid0106/Mid0107 works correctly on frames emitted by real PowerMACS controllers — the Pack bugs are one-directional. But Mid0002.Parse() is broken in both directions for PowerMACS 4000.
I don’t know if it’s because the controller firmware version is too low. Please answer it when you have time. I think this is a very good project and has helped me solve a lot of problems!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions