📄 src/models/pid.ts
D-OPEN SOVEREIGN
1/*
2 * Copyright (c) 2026 DataPond D-Library Pty Ltd. ("The Sanctuary")
3 * Non-Profit Public Library — The World Library
4 * 
5 * D-SAFE Certification issued by POND Enterprise.
6 * Published under the D-Open Code Sovereign Licence v1.0 framework
7 * DataPond D-Library All exclusive Right reserved on modifiying the code - CC Attribution to data pond, Non modifiable, Non Commercial.
8 * https://registry.world.bibliotech.com/licence
9 * Source code donated to DataPond D-Library by it's author: data pond.
10 * 
11 * Technical Guardian ("The Shield"): Pond Enterprise Pty Ltd. (ACN 694 747 987)
12 * All liability claims about D-SAFE certification issues coming from the published D-Safe direction must be addressed with Pond Enterprise Pty Ltd.
13 * Code Modification automatically voids the certified liability protection provided by Pond Enterprise.
14 */
15const BASE34_ALPHABET = '0123456789ABCDEFGHJKLMNPQRSTUVWXYZ';
16const ISO_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*';
17
18function computeMod37_2(input: string): string {
19  let p = 0;
20  for (let i = 0; i < input.length; i++) {
21    const val = ISO_ALPHABET.indexOf(input[i]);
22    if (val === -1) {
23      throw new Error(`[pid] Invalid character for ISO 7064 MOD 37-2: ${input[i]}`);
24    }
25    let s = (p + val) % 37;
26    if (s === 0) s = 37;
27    p = (s * 2) % 37;
28  }
29  const checkVal = (38 - p) % 37;
30  let checkChar = ISO_ALPHABET[checkVal];
31
32  if (checkChar === '*') checkChar = 'X';
33  if (checkChar === 'I') checkChar = 'J';
34  if (checkChar === 'O') checkChar = 'P';
35
36  return checkChar;
37}
38
39export function base34(wireId: number): string {
40  if (!Number.isInteger(wireId) || wireId < 0 || wireId > 4294967295) {
41    throw new Error(`[pid] wireId ${wireId} must be an unsigned 32-bit integer`);
42  }
43  if (wireId === 0) return '0000000';
44  
45  let str = '';
46  let val = wireId;
47  while (val > 0) {
48    str = BASE34_ALPHABET[val % 34] + str;
49    val = Math.floor(val / 34);
50  }
51  return str.padStart(7, '0');
52}
53
54export function unbase34(str: string): number {
55  if (str.length !== 7) {
56    throw new Error(`[pid] Base34 body must be exactly 7 characters, got ${str.length}`);
57  }
58  let val = 0;
59  for (let i = 0; i < str.length; i++) {
60    const idx = BASE34_ALPHABET.indexOf(str[i]);
61    if (idx === -1) {
62      throw new Error(`[pid] Invalid character in base-34 body: ${str[i]}`);
63    }
64    val = val * 34 + idx;
65  }
66  return val;
67}
68
69export function encodePID(typePrefix: string, wireId: number): string {
70  if (typePrefix.length !== 2) {
71    throw new Error(`[pid] typePrefix must be exactly 2 characters`);
72  }
73  const body = base34(wireId);
74  const check = computeMod37_2(typePrefix + body);
75  return typePrefix + body + check;
76}
77
78export function decodePID(typePrefix: string, pid: string): { wireId: number } {
79  if (!pid.startsWith(typePrefix)) {
80    throw new Error(`[pid] PID ${pid} does not start with expected prefix ${typePrefix}`);
81  }
82  if (pid.length !== 10) {
83    throw new Error(`[pid] PID must be exactly 10 characters long, got ${pid.length}`);
84  }
85  
86  const body = pid.slice(2, 9);
87  const providedCheck = pid[9];
88  
89  const expectedCheck = computeMod37_2(typePrefix + body);
90  if (providedCheck !== expectedCheck) {
91    throw new Error(`[pid] Invalid check character in PID ${pid}. Expected ${expectedCheck}`);
92  }
93
94  return { wireId: unbase34(body) };
95}
96