Skip to content

Cannot decode a GS1 element string, and the AI table covers 5 of 224 AIs #164

Description

@LKSNDRTMLKV

What is missing

DigitalLink::parse reads a Digital Link URI. It cannot read the element string a scanner actually emits — the FNC1-separated AI data behind a ]d2 / ]C1 symbology identifier. Anything integrating a physical scanner has to convert that itself before it can talk to us.

Two things found while scoping it

1. The AI table is hand-written and covers 5 of 224 AIs

digital_link/ai.rs defines AI_TABLE with five entries: 01, 22, 10, 21, 235. GS1's published Syntax Dictionary defines 224. Unknown AIs are rejected with UnknownApplicationIdentifier.

2. We accept one of GS1's sixteen Digital Link primary keys

GS1 marks these AIs as valid DL primary keys (dlpkey in the dictionary):

00 01 253 255 401 402 414 415 417 8003 8004 8006 8010 8013 8017 8018

DigitalLink::parse looks for 01 and returns MissingGtin otherwise. So a conformant link keyed on an SSCC (00), a GRAI (8003), a GIAI (8004) or a GLN (414) is refused.

This is the failure direction our GS1 oracle warns about in its own header — "we reject, GS1 accepts — the quiet one, which loses interoperability with no error ever logged." The oracle cannot catch it. Its corpus is generated by our own builder, so it only ever contains links we can already build. It proves everything we emit is valid GS1; it says nothing about valid GS1 we refuse.

Approach: generate the table, do not write one

The AI length rules must not be hand-written. Getting a fixed-vs-variable length wrong silently truncates or over-reads scanned data, and a table typed from memory is exactly the kind of invented detail that has cost this project before.

GS1 publishes the authority: gs1-syntax-dictionary, Apache-2.0 (compatible with this crate), a single ~30 KB machine-readable text file. It encodes precisely what is needed:

01         *?  N14,csum,gcppos2   ex=255,37 dlpkey=22,10,21|235   # GTIN
10          ?  X..20              req=01,02,03,8006,8026          # BATCH/LOT
21             X..20              req=01,03,8006 ex=235           # SERIAL
235            X..28              req=01                          # TPX

N14 fixed-length numeric, X..20 variable up to 20, plus check-digit (csum), primary-key (dlpkey) and mutual-exclusion (ex=) flags.

So: vendor the dictionary, generate the AI table from it, and derive both the element-string parser and the existing URI parser from the same generated source. That replaces the hand-written table rather than adding a second one beside it.

Shape

  • ElementString::parse(&str) — strips the symbology identifier, splits AIs on the dictionary's length rules, terminates variable-length AIs at FNC1 (\x1D) or end of input.
  • Every AI is retained, including ones we do not model as typed fields. Silently dropping data read off a physical product is the dangerous failure.
  • ElementString::to_digital_link(resolver_base) for the conversion callers actually want.

Closing the oracle's blind spot

The corpus must stop being built solely from our own output. Add element strings and primary keys we do not currently support, assert our verdict, and let the engine judge — so "we reject, GS1 accepts" becomes a red build instead of silence.

Done when

A scanner element string decodes to its AIs and to a Digital Link; the AI table is generated from GS1's dictionary rather than hand-maintained; every dlpkey AI either parses or is refused with a reason that is not MissingGtin; and the oracle corpus contains cases our own builder cannot produce.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions