{"_id":"@calumk/ioddforge-checker","name":"@calumk/ioddforge-checker","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@calumk/ioddforge-checker","version":"1.0.0","description":"Compute and verify the <Stamp crc> checksum of IO-Link IODD files. Zero dependencies, works in Node, Bun and the browser.","type":"module","license":"MIT","author":{"name":"Calum Knott","email":"calum@calumk.com"},"publishConfig":{"access":"public"},"main":"./src/index.js","module":"./src/index.js","exports":{".":"./src/index.js","./crc":"./src/crc.js"},"bin":{"ioddforge-crc":"src/cli.js"},"scripts":{"test":"bun test test/crc.test.js test/corpus.test.js","test:node":"node --test test/node.test.js"},"repository":{"type":"git","url":"git+https://github.com/calumk/IODDForge_Checker.git"},"bugs":{"url":"https://github.com/calumk/IODDForge_Checker/issues"},"homepage":"https://github.com/calumk/IODDForge_Checker#readme","keywords":["io-link","iodd","crc","crc32","checksum","stamp","industrial","fieldbus","automation","iolink"],"engines":{"node":">=18"},"sideEffects":false,"_id":"@calumk/ioddforge-checker@1.0.0","gitHead":"758690855a570621af9c55667cb8a0ec333a9816","_nodeVersion":"24.3.0","_npmVersion":"11.4.2","dist":{"integrity":"sha512-mlG3rkMvEF5OEvqCf4mhpJQpA1prt1jw4wo/59en7kg5r7KASxgHMZ0F2ktHSLrhTwBQsWPTfAfwsg8JHhmAJQ==","shasum":"663ce2ae6b8baeffbb73760d9a8409039a16a913","tarball":"https://registry.npmjs.org/@calumk/ioddforge-checker/-/ioddforge-checker-1.0.0.tgz","fileCount":6,"unpackedSize":25798,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIGSkX7BAMi4QVEoTRy0FqOx6f0Q9OfZaXMQI2nXvir+2AiEA3ot5PFkuxG+95H5e8vbrlSR5FjCzYFTrNmUgm9YRLVA="}]},"_npmUser":{"name":"calumk","email":"calum@calumk.com"},"directories":{},"maintainers":[{"name":"calumk","email":"calum@calumk.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ioddforge-checker_1.0.0_1786308658575_0.9663222775941478"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-09T20:50:58.345Z","1.0.0":"2026-08-09T20:50:58.733Z","modified":"2026-08-09T20:50:58.998Z"},"maintainers":[{"name":"calumk","email":"calum@calumk.com"}],"description":"Compute and verify the <Stamp crc> checksum of IO-Link IODD files. Zero dependencies, works in Node, Bun and the browser.","homepage":"https://github.com/calumk/IODDForge_Checker#readme","keywords":["io-link","iodd","crc","crc32","checksum","stamp","industrial","fieldbus","automation","iolink"],"repository":{"type":"git","url":"git+https://github.com/calumk/IODDForge_Checker.git"},"author":{"name":"Calum Knott","email":"calum@calumk.com"},"bugs":{"url":"https://github.com/calumk/IODDForge_Checker/issues"},"license":"MIT","readme":"# IODDForge Checker\n\n**Compute and verify the `<Stamp crc>` checksum of IO-Link IODD files.**\n\nZero dependencies. Plain JavaScript ES modules. Runs in Node, Bun, Deno and the\nbrowser.\n\nEvery IODD (IO Device Description) file ends with a block like this:\n\n```xml\n<Stamp crc=\"1462814215\">\n  <Checker name=\"IODD-Checker V1.1.1\" version=\"V1.1.1.0\"/>\n</Stamp>\n```\n\nThat `crc` is mandatory. Change one byte anywhere in the file and the stamp is\ninvalid — masters, engineering tools and the IODDfinder upload process will\nreject it. Until now the only way to produce a correct one was to run a\nclosed-source Windows tool, because **no open-source implementation of this\nalgorithm existed**.\n\nThis is that implementation. It reproduces the official checksum byte-for-byte\non every file tested, across four generations of the vendor checker\n(V1.1.1, V1.1.5, V1.1.13 and V2025.1).\n\n> Not affiliated with, or endorsed by, the IO-Link Community.\n> \"IO-Link\" and \"IODD\" are trademarks of their respective owners.\n\n---\n\n## Install\n\n```bash\nbun add @calumk/ioddforge-checker\n# or\nnpm install @calumk/ioddforge-checker\n```\n\nOr just copy [`src/crc.js`](src/crc.js) into your project. It is a single\nself-contained file with no imports.\n\n---\n\n## Command line\n\n```bash\n# Check that stored CRCs are correct (exit code 1 on any mismatch)\nioddforge-crc verify ./my-device-IODD1.1.xml\nioddforge-crc verify ./iodd-folder/\n\n# Print the CRC a file should have\nioddforge-crc compute ./my-device-IODD1.1.xml\n\n# Rewrite the crc attribute in place\nioddforge-crc write ./iodd-folder/\n\n# Full detail, including which checker stamped the file\nioddforge-crc info ./my-device-IODD1.1.xml\n```\n\n```\n$ ioddforge-crc verify ./examples/\n\n  ok    examples/Balluff-BNI_IOL-800-000-Z036-20190215-IODD1.1.xml\n  ok    examples/IO-Link-04-ExternalLangDevice-20180101-IODD1.1.xml\n  ok    examples/IO-Link-04-ExternalLangDevice-20180101-IODD1.1-de.xml\n  ok    examples/IO-Link-04-ExternalLangDevice-20180101-IODD1.1-zh.xml\n\n  4/4 valid\n```\n\nAdd `--json` for machine-readable output. Add `--main <file>` if a translation\nfile's main IODD cannot be inferred from its filename.\n\n`write` processes main IODDs before translations, so a whole directory\nre-stamps correctly in one pass.\n\n---\n\n## Library\n\n```js\nimport {\n  computeStampCrc,\n  verifyStampCrc,\n  applyStampCrc,\n  isExternalTextDocument,\n} from '@calumk/ioddforge-checker';\n\nimport { readFileSync, writeFileSync } from 'node:fs';\n\nconst bytes = new Uint8Array(readFileSync('device-IODD1.1.xml'));\n\n// Is the stamp correct?\nconst { valid, stored, computed } = verifyStampCrc(bytes);\n\n// Write a corrected stamp. Only the digits change — every other byte,\n// including the BOM, indentation and line endings, is preserved exactly.\nconst { bytes: stamped, crc } = applyStampCrc(bytes);\nwriteFileSync('device-IODD1.1.xml', stamped);\n```\n\n### External text documents\n\nTranslation files (`<ExternalTextDocument>` root) are chained to their main\nIODD. Stamp the main file first, then pass its CRC:\n\n```js\nconst { crc: mainCrc } = applyStampCrc(mainBytes);\nconst { bytes: deStamped } = applyStampCrc(germanBytes, { mainIoddCrc: mainCrc });\n```\n\nThis means editing the main IODD invalidates every translation. That is by\ndesign — it binds the language pack to a specific revision of the device\ndescription.\n\n### API\n\n| Function | Description |\n| --- | --- |\n| `crc32(bytes)` | Plain CRC-32/ISO-HDLC (same as zlib/PNG). |\n| `new Crc32()` | Streaming variant: `.update(bytes)`, `.value`, `.reset()`. |\n| `computeStampCrc(bytes, { mainIoddCrc })` | The IODD stamp CRC. Throws if the file has no stamp. |\n| `verifyStampCrc(bytes, opts)` | `{ valid, stored, computed }`. |\n| `applyStampCrc(bytes, opts)` | `{ bytes, crc, previous }`. Byte-preserving rewrite. |\n| `findStampCrc(bytes)` | Locate the attribute: `{ valueStart, valueEnd, value }`. |\n| `isExternalTextDocument(bytes)` | Does this file need a `mainIoddCrc`? |\n| `readChecker(bytes)` | `{ name, version }` of the tool that last stamped the file. |\n\nAll functions take and return `Uint8Array` — **never** a decoded string. See\nthe gotchas below for why.\n\n---\n\n## The algorithm\n\nFrom IODD Specification 10.012, in full:\n\n1. Use **CRC-32** as defined in ITU-T V.42 §8.1.1.6.2 / ISO/IEC 13239:2002.\n   This is exactly the ordinary zlib/PNG CRC-32: polynomial `0x04C11DB7`\n   (reflected `0xEDB88320`), init `0xFFFFFFFF`, reflect in and out,\n   final XOR `0xFFFFFFFF`.\n2. Read the file in **binary mode**.\n3. Feed bytes into the CRC up to and **including** the literal string\n   `<Stamp crc=\"`.\n4. **Skip** the attribute's digits entirely.\n5. Resume at the closing `\"` and hash through to end of file.\n6. If — and only if — the root element is `<ExternalTextDocument>`, then append\n   the ASCII decimal digits (no leading zeroes) of the **main IODD's** CRC.\n7. The result is an unsigned 32-bit integer, written in decimal.\n\n### Gotchas\n\nEach of these cost a full round of brute-force searching to discover:\n\n- **Do not normalise line endings.** Real IODDs are CRLF. Converting to LF\n  silently breaks the checksum.\n- **Do not strip the UTF-8 BOM.** It is part of the hashed content. Vendor files\n  frequently have one.\n- **Do not add or trim a trailing newline.** Some files have one, some don't;\n  both are hashed as-is.\n- **Do not round-trip through an XML parser** before hashing. Any reserialisation\n  — attribute order, self-closing tags, entity escaping, whitespace — changes\n  the bytes.\n- **Only `ExternalTextDocument` gets the appended CRC.** `IODevice`,\n  `IODDStandardDefinitions` and `IODDStandardUnitDefinitions` do not.\n- **Non-ASCII content is a red herring.** A file with 217 CJK characters hashes\n  correctly with no special handling, because everything is treated as opaque\n  UTF-8 bytes.\n\n---\n\n## Verification\n\nValidated against **40 officially stamped documents**:\n\n| Source | Count |\n| --- | --- |\n| IO Device Description Guideline examples | 24 |\n| Common Profile examples | 4 |\n| `IODD-StandardDefinitions1.1.xml` + 9 language variants | 10 |\n| `IODD-StandardUnitDefinitions1.1.xml` | 1 |\n| Balluff BNI IOL-800-000-Z036 (real vendor IODD) | 1 |\n\nOf these, 11 are `ExternalTextDocument` translations exercising the chained-CRC\npath.\n\nEvery file passes three checks:\n\n1. the officially stamped value is reproduced exactly;\n2. re-stamping the file is **byte-identical** to the original;\n3. flipping a single bit anywhere is detected.\n\nStamps in the corpus were produced by checker versions **V1.1.1**, **V1.1.5**,\n**V1.1.13** and **V2025.1** — the algorithm has been stable across all of them.\n\n### Running the tests\n\n```bash\nbun test\n```\n\nThe unit tests are self-contained. The corpus test is skipped unless you supply\nyour own copy of the reference files, which are copyright IO-Link Community and\nare **not** redistributed here:\n\n```bash\nIODD_CORPUS=/path/to/iodd-files bun test\n```\n\nGet suitable files from the IO Device Description Guideline example package, or\ndownload any vendor IODD from [ioddfinder.io-link.com](https://ioddfinder.io-link.com).\n\n---\n\n## A myth, busted\n\nA widely-cited Stack Overflow answer claims the shipped IODD-Checker uses a\ndifferent algorithm from the one in the specification, and that the documented\nprocedure cannot be made to work.\n\nThat is **not true** for the CRC. The specification text is exactly accurate —\nevery word of it. The reason it looks wrong is that it is easy to violate one of\nthe gotchas above (usually the BOM or the line endings) without realising, and\nthe resulting mismatch gets blamed on the spec rather than on the reader.\n\nA fuller explanation of the algorithm is in\n[`docs/how-the-iodd-crc-works.md`](docs/how-the-iodd-crc-works.md).\n\n---\n\n## Why this exists\n\nThis started as one piece of a larger project: a free, browser-based IODD editor\nto replace the proprietary Windows-only tooling that currently gates IO-Link\ndevice development. Producing a valid file is pointless if you cannot stamp it,\nso the CRC had to be solved first.\n\nIt turned out to be interesting enough — and useful enough on its own — to\npublish separately.\n\n## Licence\n\nMIT © Calum Knott\n","readmeFilename":"README.md","_rev":"1-9f77cca66e4981851d3e2ce3a59fcac6"}