import { getLocalizedString } from "@olenbetong/appframe-core"; export type GiaiProps = { assetReference: string | number | bigint; companyPrefix: string | number | bigint; filter?: string | number | undefined; }; /** * GIAI-96 encoder and decoder as defined by the GS1 EPC Tag data standard */ export default class Giai { /// Bit lengths for company prefix given partition equal to index. /// /// Partition Company prefix Individual asset reference /// Bits Digits Bits Digits /// 0 40 12 42 13 /// 1 37 11 45 14 /// 2 34 10 48 15 /// 3 30 9 52 16 /// 4 27 8 55 17 /// 5 24 7 58 18 /// 6 20 6 62 19 static PARTITION_TABLE = [40, 37, 34, 30, 27, 24, 20]; // EPC header for GIAI-96 coding scheme static EPC_HEADER = "00110100"; // Prefix for GS1 key used in barcodes; static BARCODE_PREFIX = "8004"; // Group separator used to separate company prefix and asset reference (FNC1 in GS1-128 barcodes) static GROUP_SEPARATOR = String.fromCodePoint(0x001d); static URN_PREFIX = "urn:epc:id:giai:"; static fromHex = (hexIdentifier: string) => { const bitIdentifier = hexToBitString(hexIdentifier); if (bitIdentifier.substring(0, 8) !== Giai.EPC_HEADER) { throw new Error("EPC value is not a valid GIAI value."); } const partition = Number.parseInt(bitIdentifier.substr(11, 3), 2); const companyPrefixLength = Giai.PARTITION_TABLE[partition]; const filter = Number.parseInt(bitIdentifier.substring(8, 11), 2); const companyPrefix = BigInt(`0b${bitIdentifier.substring(14, 14 + companyPrefixLength)}`); const assetReference = BigInt(`0b${bitIdentifier.substring(14 + companyPrefixLength)}`); return new Giai({ assetReference, companyPrefix, filter }); }; /** * * @param {string | null | undefined} barcodeString * @returns */ static fromBarcode = (barcodeString: string) => { const prefixLength = Giai.BARCODE_PREFIX.length; if (barcodeString[0] === Giai.GROUP_SEPARATOR) { barcodeString = barcodeString.substr(1); } if (barcodeString.substr(0, prefixLength) !== Giai.BARCODE_PREFIX) { throw new Error(`Barcode is not a valid GIAI barcode. (${Giai.BARCODE_PREFIX} prefix expected)`); } let giaiString = barcodeString.substring(prefixLength); const gsIndex = giaiString.indexOf(Giai.GROUP_SEPARATOR); if (gsIndex >= 0) { return new Giai({ assetReference: giaiString.substring(gsIndex + 1), companyPrefix: giaiString.substring(0, gsIndex), }); } // If no separator is found, assume a company prefix length of 7 return new Giai({ assetReference: giaiString.substring(7), companyPrefix: giaiString.substring(0, 7), }); }; /** * Creates a GIAI instance from a purse identity URI (urn:epc:giai:[companyprefix].[assetreference]) * @param {string} uri EPC GIAI pure identity URI */ static fromEpcPureIdentityUri(uri: string) { if (!uri.startsWith(Giai.URN_PREFIX)) { throw Error("URI is not a valid GIAI URI"); } let [companyPrefix, assetReference] = uri.substring(Giai.URN_PREFIX.length).split("."); return new Giai({ assetReference, companyPrefix, }); } /** * Attempt to create a GIAI instance from a string with unknown format. Could be either a URN, barcode value * or EPC value. * @param {string} value * @returns */ static fromString(value: string, ignoreError?: false): Giai; static fromString(value: string, ignoreError: true): Giai | undefined; static fromString(value: string, ignoreError = false) { try { if (value.startsWith("0x")) { value = value.replace("0x", ""); return Giai.fromHex(value.substring(0, 24)); // return Giai.fromHex(value.substring(6, value.length - 4)); } else if (value.startsWith(Giai.URN_PREFIX)) { return Giai.fromEpcPureIdentityUri(value); } else if (value.startsWith(Giai.BARCODE_PREFIX)) { return Giai.fromBarcode(value); } else if (value.length === 24) { return Giai.fromHex(value); } } catch (ex) { if (!ignoreError) { throw ex; } } if (!ignoreError) { throw Error( getLocalizedString( "Can't create GIAI from string. Failed to match value '{value}' with known input formats.", ).replace("{value}", value), ); } return undefined; } /** * @type {string | number | bigint} */ #assetReference; /** * @type {string | number | bigint} */ #companyPrefix; /** * * @param {GiaiProps} options */ constructor({ assetReference, companyPrefix }: GiaiProps) { this.#assetReference = assetReference; this.#companyPrefix = companyPrefix; } get assetReference() { return this.#assetReference; } get companyPrefix() { return this.#companyPrefix; } // Returns the EPC pure identity uri for this identifier - as used by EPCIS. toEpcPureIdentityUri() { return `${Giai.URN_PREFIX}${this.companyPrefix}.${this.assetReference}`; } toGiaiBarcode() { return Giai.BARCODE_PREFIX + this.companyPrefix + Giai.GROUP_SEPARATOR + this.assetReference; } toEpcHex() { let bitIdentifier = Giai.EPC_HEADER; bitIdentifier += "000"; // Filter bitIdentifier += (5).toString(2).padStart(3, "0"); // Partition let companyPrefix = ["bigint", "number"].includes(typeof this.#companyPrefix) ? this.#companyPrefix : BigInt(this.#companyPrefix); let assetReference = ["bigint", "number"].includes(typeof this.#assetReference) ? this.#assetReference : BigInt(this.#assetReference); bitIdentifier += companyPrefix.toString(2).padStart(24, "0"); bitIdentifier += assetReference.toString(2).padStart(58, "0"); return BigInt(`0b${bitIdentifier}`).toString(16).toUpperCase(); } } export function hexToBitString(hexString: string) { let bitString = ""; for (let i = 0; i < hexString.length; i += 2) { const byteString = hexString.substr(i, 2); const byteBits = Number.parseInt(byteString, 16).toString(2); bitString += byteBits.padStart(8, "0"); } return bitString; } declare global { interface Window { Giai: typeof Giai | undefined; } } window.Giai = Giai;