{
  "markdown": "# go-secs\n\n`go-secs` is a Go library for semiconductor-equipment communication. It implements\n[SECS-II](https://en.wikipedia.org/wiki/SECS-II) (SEMI E5),\n[HSMS / HSMS-SS](https://en.wikipedia.org/wiki/High-Speed_SECS_Message_Services) (SEMI E37 / E37.1),\nand SECS-I over TCP/IP (SEMI E4), together with an SML (SECS Message Language) parser.\n\n![Test Status](https://github.com/arloliu/go-secs/actions/workflows/ci.yaml/badge.svg)\n[![Go Reference](https://pkg.go.dev/badge/github.com/arloliu/go-secs/v2.svg)](https://pkg.go.dev/github.com/arloliu/go-secs/v2)\n[![Go Report Card](https://goreportcard.com/badge/github.com/arloliu/go-secs/v2)](https://goreportcard.com/report/github.com/arloliu/go-secs/v2)\n\n> This is the **v2** module (`github.com/arloliu/go-secs/v2`), built around an immutable-message\n> model. Every message and data item is immutable and safe to share across goroutines.\n>\n> Need v1 (`github.com/arloliu/go-secs`, no `/v2` suffix)? It's in maintenance mode — see the\n> [v1 branch](https://github.com/arloliu/go-secs/tree/v1) or\n> [pkg.go.dev/github.com/arloliu/go-secs](https://pkg.go.dev/github.com/arloliu/go-secs).\n\n## Supports\n\n* SECS-I over TCP/IP (SEMI E4)\n* SECS-II (SEMI E5)\n* HSMS (SEMI E37), HSMS-SS (SEMI E37.1)\n* SML — parses single/double-quoted stream-functions, an optional message name, single/double-quoted\n  ASCII items, hex byte literals, and escape sequences\n\n## Features\n\n### SECS-II Operations\n\n* **Comprehensive data item support:** integers, floating-point numbers, booleans, binary, lists,\n  ASCII strings, JIS-8, and Localized Character Strings (FormatCode 0o22) with a configurable\n  encoding scheme (UTF-8, UCS-2, Shift-JIS, and others via the Localized String Header).\n* **Immutable and concurrency-safe:** every `secs2.Item` is immutable and no method exposes mutable\n  internal storage, so items can be shared freely across goroutines. Constructors never panic — bad\n  input yields an item carrying a deferred error you inspect with `Error()`.\n* **Serialization:** encode an item to its SECS-II wire bytes with `ToBytes`, or append into an\n  existing buffer with `AppendTo`.\n* **SML generation:** render an item to SML text with `ToSML`.\n* **Typed path access:** `secs2.Cursor` reads a nested item tree with a single chained call —\n  `secs2.NewCursor(item).At(1, 0).ASCII()` — instead of the `Get` / type-assert / `ToXxx` / index dance.\n  A failed hop is remembered rather than panicking, so a multi-hop extraction needs only one error check at the end.\n\n### HSMS / HSMS-SS Communication\n\n* **HSMS-SS (Single Session):** a streamlined implementation of the SEMI E37.1 single-session mode.\n* **Active and passive modes:** connect as a TCP client (active) or listen as a TCP server (passive).\n* **Connection is the endpoint:** `hsms.Connection` embeds `hsms.SECS2Endpoint`, so you send\n  messages, reply, and register handlers directly on the connection.\n  The HSMS session ID is configured per connection.\n  Inbound messages can also be received on a channel via `AddDataMessageChan`, as an alternative\n  to `AddDataMessageHandler`.\n* **Connection-state management:** an explicit state machine (`hsms.ConnState`) with registrable\n  state-change handlers.\n  `SubscribeLifecycle` is the cancellable, cause-carrying counterpart:\n  it reports each transition together with the `TransitionCause` that drove it —\n  a local Close, a peer Separate, a T7 expiry, a linktest failure, a dropped socket, and so on.\n* **Transaction observability:** `hsms.WithTransactionObserver` reports a `TxEvent` for every completed synchronous send.\n  The event names the stream, function, duration, and outcome —\n  enough to feed a metrics histogram or trace exporter without hand-instrumenting each call site.\n* **Resilience:** automatic reconnection, and an auto-linktest with a configurable failure threshold\n  for tolerating transient T6 timeouts. Activity-based linktest suppression (on by default) probes\n  only idle links and does not count a probe timeout toward the disconnect threshold when the\n  failure evaluation observes signs of life — protecting slow, aged equipment busy with a long command from probe-induced\n  disconnects, while a silent dead link is still dropped within a bounded time. See the\n  [linktest suppression guide](docs/guides/linktest-suppression.md) for the behavior contract,\n  tuning, trade-offs, and precise detection bounds; disable with\n  `hsms.WithLinktestSuppression(false)`.\n* **Metrics:** live atomic counters (sent/received/error data messages, linktests, retries) via\n  `Connection.Metrics()`.\n* **Reply matching:** every candidate reply is checked against SEMI E37 §9.4.1's Stream/Function\n  fields, and a mismatch is always counted in `ConnectionMetrics.ReplyMismatchCount()`.\n  Enforcement is opt-in via `hsms.WithStrictReplyMatching(true)`, which turns a mismatched reply\n  into a miss that stalls the transaction to T3 instead of completing it —\n  enable it only once `ReplyMismatchCount` stays at 0 under the default.\n* **Diagnostics:** `DataMessage.TrailingBytes()` reports how many bytes followed the first decoded\n  SECS-II item, for equipment that pads a frame's length around a fixed buffer rather than its\n  actual encoded item.\n* **Error handling:** a peer `Reject.req` is surfaced to the caller as an `*hsms.RejectError`.\n  A send is rejected locally, before it reaches the wire, in three cases: an even-function primary\n  (`ErrEvenFunctionPrimary` — SEMI E5 §7.2 requires an odd primary function);\n  a `SendDataMessageAsync` call with `replyExpected: true` (`ErrAsyncReplyExpected`, since an\n  async send never opens a reply-wait transaction); or a frame that would exceed\n  `hsms.MaxMessageSize` (`ErrMessageTooLarge`).\n  `hsms.IsTransient` and `hsms.IsTimeout` classify any error returned from the send/lifecycle surface,\n  so a caller can decide whether a failed call is worth retrying without hand-rolling its own `errors.Is` chain.\n\n### SECS-I over TCP/IP Communication\n\n* **SEMI E4 compliant:** the SECS-I block-transfer and message protocols over a TCP/IP stream.\n* **Half-duplex protocol:** the ENQ/EOT/ACK/NAK handshake for line-direction control.\n* **Contention resolution:** Master/Slave contention per SEMI E4 (Equipment = Master, Host = Slave).\n* **Multi-block messages:** large messages are split into blocks (≤ 244 body bytes each) and\n  reassembled on receive.\n* **Configurable timeouts:** T1 (inter-character), T2 (protocol), and T4 (inter-block) on the line,\n  plus the T3 reply timeout shared with the core.\n* **Duplicate detection:** duplicate blocks are detected and discarded.\n* **Active and passive modes:** TCP client (active) and TCP server (passive).\n* **Automatic reconnection:** the active side re-dials after a line failure.\n* **Unified interface:** the same `hsms.Connection` interface as HSMS-SS, so application code is\n  transport-agnostic.\n\n### SML Operations\n\n* **Parsing:** `sml.Parse` turns SML text into HSMS data messages.\n* **Formats:** an optional message name, single- or double-quoted stream-functions and ASCII items,\n  hex byte literals, and escape sequences.\n* **Strict mode:** `sml.ParseStrict` adheres to the ASCII standard and treats escape characters\n  literally.\n\n> See the [SML document](sml/README.md) for details.\n\n## Packages\n\n* **secs1** — SECS-I over TCP/IP (SEMI E4). Returns the same `hsms.Connection` as `hsmsss`.\n* **secs2** — SECS-II data items and messages.\n* **hsms** — the shared message model, the `Connection`/`SECS2Endpoint` interfaces, HSMS message\n  encoding/decoding, and the connection engine.\n* **hsmsss** — HSMS-SS (Single Session) transport per SEMI E37.1.\n* **sml** — the SML parser.\n* **gem** — helpers for constructing common GEM (SEMI E30) messages, plus generated body decoders\n  (`gem.DecodeS1F14`, `gem.DecodeS6F11`, …) that read a received reply back into a typed result struct.\n* **logger** — a small logging façade for integrating your own logging framework.\n\n## Performance\n\n`v2` is benchmarked against the latest `v1` release with a standalone module (see\n[`benchmarks/`](benchmarks/)): a real active/passive HSMS-SS connection over loopback TCP, plus\n`secs2.Item` construct/encode/decode microbenchmarks.\n\n* **Every full-connection round trip is faster than v1** — 15% to 61% less time, since v2 avoids\n  v1's pooling/`Free()` bookkeeping on the hot path.\n* **`secs2.Decode` always copies its input** (v1 aliased it), so the returned `Item` never depends\n  on the caller's buffer. For a buffer the caller already owns outright (e.g. one just read from a\n  socket or a file), `secs2.DecodeOwned` skips that copy and matches v1's decode performance —\n  including for ASCII/JIS-8/localized-string payloads, not just binary.\n* **Single-value `IntItem`/`UintItem`/`FloatItem`/`BooleanItem` decode without allocating a backing\n  slice** — a scalar fast path in `secs2.Decode`/`DecodeOwned` stores the lone value inline instead.\n  Roughly 18-20% faster and one fewer allocation per scalar item decoded, up to ~42% fewer\n  allocations on payloads dominated by single-value items (e.g. a mixed-type record).\n* Hot atomic counters in `hsms.ConnectionMetrics` / `secs1.ConnectionMetrics` are cache-line padded\n  to prevent false sharing between counters under concurrent access.\n* Reproduce it yourself: `cd benchmarks && make bench-v1 bench-v2 compare`, or from that same\n  `benchmarks/` directory, `go test ./secs2item/v2/... -bench . -benchmem` against a prior commit\n  for a focused before/after.\n\n## Message and Item Object Model\n\n* **`secs2.SECS2Message`** defines the core of a SECS-II message: stream code, function code, W-bit,\n  and the SECS-II data item.\n* **`hsms.Message`** is the read-only interface implemented by every immutable HSMS message; both\n  `hsms.DataMessage` and `hsms.ControlMessage` satisfy it. `DataMessage` carries SECS-II data;\n  `ControlMessage` manages the HSMS connection.\n* **`secs2.Item`** is the unified interface for SECS-II data items. All data types implement it.\n\n```text\nhsms.Message (interface)\n├── hsms.DataMessage\n└── hsms.ControlMessage\n\nsecs2.Item (interface)\n├── ASCIIItem          (NewASCIIItem)\n├── BinaryItem         (NewBinaryItem)\n├── BooleanItem        (NewBooleanItem)\n├── FloatItem          (NewFloatItem;  shortcuts F4, F8)\n├── IntItem            (NewIntItem;    shortcuts I1, I2, I4, I8)\n├── UintItem           (NewUintItem;   shortcuts U1, U2, U4, U8)\n├── JIS8Item           (NewJIS8Item)\n├── LocalizedStrItem   (NewLocalizedStrItem, NewUTF8StrItem)\n└── ListItem           (NewListItem)\n```\n\n## Usage\n\n### Installation\n\n```bash\ngo get github.com/arloliu/go-secs/v2\n```\n\nImport the packages you need under the `github.com/arloliu/go-secs/v2/` path, for example\n`github.com/arloliu/go-secs/v2/hsmsss` or `github.com/arloliu/go-secs/v2/secs2`.\n\n### Working with SECS-II data items\n\n```go\nimport \"github.com/arloliu/go-secs/v2/secs2\"\n\n// Build a nested list. Constructors never panic; on bad input they return an item\n// carrying a deferred error you can inspect with Error().\nlist := secs2.NewListItem(\n    secs2.NewASCIIItem(\"test1\"),     // index 0\n    secs2.NewIntItem(4, 1, 2, 3, 4), // index 1: I4 with four values\n    secs2.NewListItem(               // index 2: nested list\n        secs2.NewASCIIItem(\"test2\"),\n        secs2.NewASCIIItem(\"test3\"),\n    ),\n)\n\n// Numeric-type shortcut constructors are also available: I1/I2/I4/I8, U1/U2/U4/U8, F4/F8.\nu := secs2.U4(256)\n\n// Get returns (Item, error). The IsX / ToX accessors report and extract typed values.\nfirst, err := list.Get(0)\nif err == nil && first.IsASCII() {\n    s, _ := first.ToASCII() // \"test1\"\n    _ = s\n}\n\n// Reach into a nested list with multiple indices.\nnested, err := list.Get(2, 1) // ASCII item \"test3\"\n_, _ = nested, u\n```\n\n### Parsing SML\n\n```go\nimport \"github.com/arloliu/go-secs/v2/sml\"\n\ninput := `MessageName:'S7F3' W\n<L\n    <A \"path\">\n    <A \"model\">\n    <A \"version\">\n    <L\n        <U4 256>\n        <A \"value\">\n    >\n>\n.`\n\nmsgs, err := sml.Parse(input)\nif err != nil {\n    // handle error\n}\n\nfor _, msg := range msgs {\n    _ = msg.Stream()   // 7\n    _ = msg.Function() // 3\n    _ = msg.WaitBit()  // true\n}\n```\n\n### HSMS-SS host (active mode)\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"log\"\n    \"time\"\n\n    \"github.com/arloliu/go-secs/v2/hsms\"\n    \"github.com/arloliu/go-secs/v2/hsmsss\"\n    \"github.com/arloliu/go-secs/v2/secs2\"\n)\n\n// handleMessage runs inline on the connection's receive goroutine. It MUST NOT block:\n// reply asynchronously with ep.ReplyDataMessage, or offload slow work to your own goroutine.\nfunc handleMessage(msg *hsms.DataMessage, ep hsms.SECS2Endpoint) {\n    if msg.Stream() == 98 && msg.Function() == 1 {\n        item, err := msg.Item()\n        if err != nil {\n            return\n        }\n        _ = ep.ReplyDataMessage(context.Background(), msg, item)\n    }\n}\n\nfunc main() {\n    ctx := context.Background()\n\n    // Build the configuration. Shared core knobs (session ID, T3–T8, linktest, logger)\n    // are passed through hsmsss.WithConnectionOption.\n    cfg, err := hsmsss.NewConfig(\"127.0.0.1\", 5000,\n        hsmsss.WithActive(), // dial outbound\n        hsmsss.WithConnectionOption(hsms.WithSessionID(1000)),\n        hsmsss.WithConnectionOption(hsms.WithT3(30*time.Second)),\n    )\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    conn, err := hsmsss.New(cfg)\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer conn.Close()\n\n    // The Connection embeds hsms.SECS2Endpoint, so handlers are registered on it directly.\n    conn.AddDataMessageHandler(handleMessage)\n\n    // Open and block until the link reaches the Selected state.\n    if err := conn.Open(ctx, hsms.OpenWaitSelected); err != nil {\n        log.Fatal(err)\n    }\n\n    // Send S99F1 with the W-bit set and wait for the reply.\n    reply, err := conn.SendDataMessage(ctx, 99, 1, true, secs2.NewASCIIItem(\"test\"))\n    if err != nil {\n        log.Fatal(err)\n    }\n    _ = reply // process the reply\n}\n```\n\n### HSMS-SS equipment (passive mode)\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"log\"\n    \"time\"\n\n    \"github.com/arloliu/go-secs/v2/hsms\"\n    \"github.com/arloliu/go-secs/v2/hsmsss\"\n    \"github.com/arloliu/go-secs/v2/secs2\"\n)\n\nfunc handleMessage(msg *hsms.DataMessage, ep hsms.SECS2Endpoint) {\n    if msg.Stream() == 99 && msg.Function() == 1 {\n        item, err := msg.Item()\n        if err != nil {\n            return\n        }\n        _ = ep.ReplyDataMessage(context.Background(), msg, item)\n    }\n}\n\nfunc main() {\n    ctx := context.Background()\n\n    cfg, err := hsmsss.NewConfig(\"127.0.0.1\", 5000,\n        hsmsss.WithPassive(), // listen for an inbound connection\n        hsmsss.WithConnectionOption(hsms.WithSessionID(1000)),\n        hsmsss.WithConnectionOption(hsms.WithT3(30*time.Second)),\n    )\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    conn, err := hsmsss.New(cfg)\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer conn.Close()\n\n    conn.AddDataMessageHandler(handleMessage)\n\n    if err := conn.Open(ctx, hsms.OpenWaitSelected); err != nil {\n        log.Fatal(err)\n    }\n\n    // Send S98F1 with the W-bit set and wait for the reply.\n    reply, err := conn.SendDataMessage(ctx, 98, 1, true, secs2.NewASCIIItem(\"test\"))\n    if err != nil {\n        log.Fatal(err)\n    }\n    _ = reply // process the reply\n}\n```\n\n### SECS-I host (active mode)\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"log\"\n    \"time\"\n\n    \"github.com/arloliu/go-secs/v2/hsms\"\n    \"github.com/arloliu/go-secs/v2/secs1\"\n    \"github.com/arloliu/go-secs/v2/secs2\"\n)\n\nfunc handleMessage(msg *hsms.DataMessage, ep hsms.SECS2Endpoint) {\n    item, err := msg.Item()\n    if err != nil {\n        return\n    }\n    _ = ep.ReplyDataMessage(context.Background(), msg, item)\n}\n\nfunc main() {\n    ctx := context.Background()\n\n    cfg, err := secs1.NewConfig(\"127.0.0.1\", 5000,\n        secs1.WithActive(),       // TCP client\n        secs1.WithHost(),         // host role (Slave per SEMI E4)\n        secs1.WithDeviceID(1),    // 15-bit device ID\n        secs1.WithT2(10*time.Second),\n        secs1.WithRetryLimit(3),\n        secs1.WithConnectionOption(hsms.WithT3(45*time.Second)), // reply timeout\n    )\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    conn, err := secs1.New(cfg)\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer conn.Close()\n\n    conn.AddDataMessageHandler(handleMessage)\n\n    if err := conn.Open(ctx, hsms.OpenWaitSelected); err != nil {\n        log.Fatal(err)\n    }\n\n    // Send S1F1 with the W-bit set and wait for the reply.\n    reply, err := conn.SendDataMessage(ctx, 1, 1, true, secs2.NewASCIIItem(\"test\"))\n    if err != nil {\n        log.Fatal(err)\n    }\n    _ = reply // process the reply\n}\n```\n\n### SECS-I equipment (passive mode)\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"log\"\n    \"time\"\n\n    \"github.com/arloliu/go-secs/v2/hsms\"\n    \"github.com/arloliu/go-secs/v2/secs1\"\n)\n\nfunc handleMessage(msg *hsms.DataMessage, ep hsms.SECS2Endpoint) {\n    item, err := msg.Item()\n    if err != nil {\n        return\n    }\n    _ = ep.ReplyDataMessage(context.Background(), msg, item)\n}\n\nfunc main() {\n    ctx := context.Background()\n\n    cfg, err := secs1.NewConfig(\"127.0.0.1\", 5000,\n        secs1.WithPassive(),     // TCP server\n        secs1.WithEquipment(),    // equipment role (Master per SEMI E4)\n        secs1.WithDeviceID(1),    // 15-bit device ID\n        secs1.WithT2(10*time.Second),\n        secs1.WithConnectionOption(hsms.WithT3(45*time.Second)),\n    )\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    conn, err := secs1.New(cfg)\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer conn.Close()\n\n    conn.AddDataMessageHandler(handleMessage)\n\n    if err := conn.Open(ctx, hsms.OpenWaitSelected); err != nil {\n        log.Fatal(err)\n    }\n\n    // Send S1F13 (Establish Communications Request) with the W-bit set and wait\n    // for the reply. A nil item sends an empty message body.\n    reply, err := conn.SendDataMessage(ctx, 1, 13, true, nil)\n    if err != nil {\n        log.Fatal(err)\n    }\n    _ = reply // process the reply\n}\n```\n",
  "bytes": 18001,
  "sha": "ba3b74308674a0c79be95bd71976b20eb17fb35645eaa012de6e7624b0ec6c76",
  "repo_slug": "arloliu/go-secs",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_arloliu_go_secs_knowledges_index_md_ccd88c66/readme"
}