How the IO-Link IODD checksum works
Every IODD (IO Device Description) file carries a checksum near the end: It is mandatory. Change one byte anywhere in the file and the stamp no longer matches, and the file will be rejected by engineering tools, by masters, and by the IODDfinder upload process. Despite the IO-Link ecosystem being nearly two decades old, there has been no open-source implementation of this algorithm - the only way to produce a valid stamp was to run the official closed-source, Windows-only IODD-Checker. This post explains exactly how the checksum is computed. Just want the code? calumk / IODDForge_Checker Compute and verify the checksum of IO-Link IODD files. Zero dependencies, works in Node, Bun and the browser. IODDForge Checker Compute and verify the checksum of IO-Link IODD files. Zero dependencies. Plain JavaScript ES modules. Runs in Node, Bun, Deno and the browser. Every IODD (IO Device Description) file ends with a block like this: That crc is mandatory. Change one byte anywhere in the file and the stamp is invalid - masters, engineering tools and the IODDfinder upload process will reject it. Until now the only way to produce a correct one was to run a closed-source Windows tool, because no open-source implementation of this algorithm existed. This is that implementation. It reproduces the official checksum byte-for-byte on every file tested, across four generations of the vendor checker (V1.1.1, V1.1.5, V1.1.13 and V2025.1). Not affiliated with, or endorsed byโฆ AI Disclaimer This repo, and the post you are about to read, was written by AI - it bares my name, but really is authored by Claud Opus 5. The algorithm The procedure is defined in IODD Specification 10.012, and the specification text is precise and complete. In summary: - Use CRC-32 as defined in ITU-T V.42 ยง8.1.1.6.2 / ISO/IEC 13239:2002. - Read the file in binary mode. - Feed bytes into the CRC up to and including the literal string , append the ASCII decimal digits of the main IODD's CRC. - The result is an unsigned 32-bit integer, written in decimal. Each of those steps hides a detail worth spelling out. It's just zlib CRC-32 The specification's reference to ITU-T V.42 sounds exotic, but that standard describes the ordinary CRC-32 everyone already has: the same one used by zlib, gzip and PNG. Formally, CRC-32/ISO-HDLC: | Parameter | Value | |---|---| | Polynomial | 0x04C11DB7 (reflected: 0xEDB88320 ) | | Initial value | 0xFFFFFFFF | | Reflect in / out | yes / yes | | Final XOR | 0xFFFFFFFF | Check ("123456789" ) | 0xCBF43926 | If a library gives you 0xCBF43926 for the string 123456789 , it is the right function. No custom polynomial is involved. The self-reference trick A checksum stored inside the file it protects is a circular definition: writing the value changes the bytes, which changes the value. The standard escape is to zero the field before hashing. IODD instead skips it. The byte stream fed to the CRC is the file with a hole punched in it, exactly where the digits live: ... โโโโโโฌโโโโโ hashed โ hashed โโโโโโโโโโโโโโโโโบโโโโโโโโโโโโโโโโโโโโโโโโโบ skipped In code, that's two calls into a streaming CRC: const stamp = findStampCrc(bytes); // locate the attribute value const crc = new Crc32(); crc.update(bytes.subarray(0, stamp.valueStart)); // through ` root, holding translations of a device's UI strings. These get one extra step. After hashing their own bytes to end of file, the decimal digits of the main IODD's CRC are appended to the stream as ASCII characters, with no leading zeroes: if (isExternalTextDocument(bytes)) { crc.update(encoder.encode(String(mainIoddCrc))); } So if the main IODD's CRC is 794805970 , the nine characters 7 , 9 , 4 , โฆ are fed in as the final bytes. This is a chaining construction, and it is deliberate. It binds a translation to one specific revision of the device description it translates. Edit the main IODD and every language file's stamp becomes invalid, which prevents a stale translation from silently pairing with a newer device model. The order of operations follows from this: stamp the main IODD first, then the translations. Only ExternalTextDocument behaves this way. IODevice , IODDStandardDefinitions and IODDStandardUnitDefinitions hash their own bytes and stop. A note on a persistent myth A frequently-cited claim holds that the shipped IODD-Checker uses a different algorithm from the published specification, and that the documented procedure cannot be made to work. Testing against 40 officially stamped documents - spanning checker versions V1.1.1, V1.1.5, V1.1.13 and V2025.1 - shows this is not the case. The specification is accurate as written, and the algorithm has been stable across roughly a decade of tool releases. The confusion is easy to account for. Every one of the "binary mode" pitfalls above produces a mismatch, and most of them happen invisibly, without any explicit line of code choosing them. A stripped BOM or a normalised newline looks like a broken specification rather than a helpful runtime. Verification The implementation in this repository is validated against: | Source | Count | |---|---| | IO Device Description Guideline examples | 24 | | Common Profile examples | 4 | IODD-StandardDefinitions1.1.xml + 9 language variants | 10 | IODD-StandardUnitDefinitions1.1.xml | 1 | | Balluff BNI IOL-800-000-Z036 (real vendor IODD) | 1 | 11 of those are ExternalTextDocument files exercising the chained path. Each file is checked three ways: the officially stamped value must be reproduced exactly, re-stamping must be byte-identical to the original, and flipping a single bit anywhere must be detected. The vendor file is disproportionately valuable as a test case, because it differs from the specification examples in four independent ways - it has a BOM, uses CRLF, has no trailing newline, and was stamped by the oldest checker version in the set. Matching it confirms all four rules at once. Using it IODDForge Checker is MIT licensed with zero dependencies, and runs in Node, Bun, Deno and the browser. ioddforge-crc verify ./my-device-IODD1.1.xml ioddforge-crc write ./iodd-folder/ import { applyStampCrc } from 'ioddforge-checker'; const { bytes, crc } = applyStampCrc(fileBytes); applyStampCrc replaces only the digits inside crc="โฆ" and leaves every other byte untouched, which keeps it safe to run against vendor files. Why it matters The stamp is the last gate between an authored IODD and a usable one. Without a way to compute it, no open-source tool can produce a file that real IO-Link tooling will accept - which has made the closed-source checker a hard dependency for anyone building in this space. It also imposes a useful discipline on editors. Because the checksum covers the raw bytes, any tool that edits IODDs is obliged to preserve formatting exactly. The mandatory checksum turns out to be a rather strict correctness test, enforced on every save. Top comments (0)
Comments
No comments yet. Start the discussion.