fiftyone.pipeline.did 4.5.38 → 4.5.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/didClient.js +5 -4
- package/examples/fodIdExample.js +32 -12
- package/fodId.js +123 -84
- package/fodIdParseError.js +1 -1
- package/index.js +2 -0
- package/internal/layout.js +77 -0
- package/internal/terms.js +172 -0
- package/node_modules/owid/owid.crypto.test.js +27 -0
- package/node_modules/owid/v1.js +15 -0
- package/package.json +10 -3
- package/readme.md +151 -20
- package/tests/didClient.integration.test.js +1 -1
- package/tests/didClient.test.js +6 -5
- package/tests/envelope.js +71 -15
- package/tests/fodId.test.js +411 -91
- package/types/didClient.d.ts +17 -2
- package/types/fodId.d.ts +48 -56
- package/types/fodIdParseError.d.ts +1 -1
- package/types/index.d.ts +2 -1
- package/types/internal/layout.d.ts +41 -0
- package/types/internal/terms.d.ts +39 -0
- package/types/usage.d.ts +34 -0
- package/usage.js +89 -0
package/didClient.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* ********************************************************************* */
|
|
22
22
|
|
|
23
23
|
const FodId = require('./fodId');
|
|
24
|
+
const layout = require('./internal/layout');
|
|
24
25
|
const IdType = require('./idType');
|
|
25
26
|
const packageVersion = require('./package.json').version;
|
|
26
27
|
|
|
@@ -777,7 +778,7 @@ function ensureEncodedLength (value) {
|
|
|
777
778
|
* @returns {Date} the moment the envelope says it was created
|
|
778
779
|
*/
|
|
779
780
|
function dateOf (fodId) {
|
|
780
|
-
return new Date(OWID_EPOCH_MS + fodId.
|
|
781
|
+
return new Date(OWID_EPOCH_MS + fodId.date * MINUTE_MS);
|
|
781
782
|
}
|
|
782
783
|
|
|
783
784
|
/**
|
|
@@ -794,9 +795,9 @@ function dateOf (fodId) {
|
|
|
794
795
|
*/
|
|
795
796
|
function payloadLengthValid (fodId) {
|
|
796
797
|
const matchKeyLength = fodId.type === IdType.RANDOM
|
|
797
|
-
?
|
|
798
|
-
:
|
|
799
|
-
return fodId.payload.length >=
|
|
798
|
+
? layout.GUID_LENGTH
|
|
799
|
+
: layout.MATCH_KEY_LENGTH;
|
|
800
|
+
return fodId.payload.length >= layout.HEADER_LENGTH + matchKeyLength;
|
|
800
801
|
}
|
|
801
802
|
|
|
802
803
|
/**
|
package/examples/fodIdExample.js
CHANGED
|
@@ -25,15 +25,20 @@
|
|
|
25
25
|
*
|
|
26
26
|
* The 51Degrees Cloud service issues real 51Dids. To keep this example
|
|
27
27
|
* self-contained and offline, it builds a sample 51Did in process - generate
|
|
28
|
-
* an ECDSA P-256 key pair, sign a canonical
|
|
29
|
-
*
|
|
28
|
+
* an ECDSA P-256 key pair, sign a canonical payload - then parses it back
|
|
29
|
+
* and prints the payload fields. It also shows the headline use
|
|
30
30
|
* case: a 51Did is re-issued fresh on every call (the envelope, hence the
|
|
31
31
|
* base64, changes), but the match key is stable. Compare match keys, never
|
|
32
32
|
* envelopes.
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
35
|
const { webcrypto } = require('crypto');
|
|
36
|
-
const { FodId, IdType } = require('../index');
|
|
36
|
+
const { FodId, IdType, Usage } = require('../index');
|
|
37
|
+
// The payload byte layout is internal to the package, so a consumer
|
|
38
|
+
// never reaches for it. This example builds a sample 51Did to work on,
|
|
39
|
+
// which the cloud would otherwise issue, so it reads the layout from
|
|
40
|
+
// inside the package.
|
|
41
|
+
const layout = require('../internal/layout');
|
|
37
42
|
|
|
38
43
|
const subtle = webcrypto.subtle;
|
|
39
44
|
const VERSION = 2;
|
|
@@ -45,14 +50,27 @@ function uint32LE (v) {
|
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
function samplePayload () {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
p
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
// One byte longer than the least length, because the terms byte follows
|
|
54
|
+
// the match key. A 51Did whose payload stops at the match key carries
|
|
55
|
+
// no terms byte and reads as Terms.NOT_STATED.
|
|
56
|
+
const p = new Uint8Array(layout.PAYLOAD_LENGTH + layout.TERMS_LENGTH);
|
|
57
|
+
// Bits 6 and 7 are zero, so the type is Probabilistic. Bits 4 and 5 are
|
|
58
|
+
// zero, so the payload version is 0, which is the layout this package
|
|
59
|
+
// reads. Bits 0 to 2 are the usage and they are cumulative, so 0b011
|
|
60
|
+
// grants standard marketing and, with it, non-marketing use.
|
|
61
|
+
p[layout.FLAGS_OFFSET] = 0b0000_0011;
|
|
62
|
+
p[layout.LICENSE_ID_OFFSET] = 0x78;
|
|
63
|
+
p[layout.LICENSE_ID_OFFSET + 1] = 0x56;
|
|
64
|
+
p[layout.LICENSE_ID_OFFSET + 2] = 0x34;
|
|
65
|
+
p[layout.LICENSE_ID_OFFSET + 3] = 0x12;
|
|
66
|
+
for (let i = 0; i < layout.MATCH_KEY_LENGTH; i++) {
|
|
67
|
+
p[layout.MATCH_KEY_OFFSET + i] = 0x20 + i;
|
|
55
68
|
}
|
|
69
|
+
// The terms document this sample was created under, being index 1, the
|
|
70
|
+
// Model Terms for Marketing version 2. The byte is an index into a table
|
|
71
|
+
// in the specification and is not a version number, and an issuer writes
|
|
72
|
+
// it for every marketing identifier.
|
|
73
|
+
p[layout.PAYLOAD_LENGTH] = 1;
|
|
56
74
|
return p;
|
|
57
75
|
}
|
|
58
76
|
|
|
@@ -94,9 +112,11 @@ async function run () {
|
|
|
94
112
|
console.log('51Did parsed from base64:');
|
|
95
113
|
console.log(' Domain :', fodId.domain);
|
|
96
114
|
console.log(' Type :', IdType.name(fodId.type));
|
|
97
|
-
console.log('
|
|
115
|
+
console.log(' Usage :', Usage.name(fodId.usage));
|
|
116
|
+
console.log(' From cons.:', fodId.usageFromConsent);
|
|
98
117
|
console.log(' LicenseId :', fodId.licenseId);
|
|
99
118
|
console.log(' Match key :', Buffer.from(fodId.matchKey).toString('hex'));
|
|
119
|
+
console.log(' Terms :', fodId.terms);
|
|
100
120
|
console.log(' Verifies :', await fodId.verify(publicPem));
|
|
101
121
|
|
|
102
122
|
// Re-issue the same payload at a later time. The envelope differs and the
|
|
@@ -129,7 +149,7 @@ async function run () {
|
|
|
129
149
|
', status ' + result.status);
|
|
130
150
|
}
|
|
131
151
|
const shortPayload = await issue(
|
|
132
|
-
keyPair.privateKey, payload.slice(0,
|
|
152
|
+
keyPair.privateKey, payload.slice(0, layout.PAYLOAD_LENGTH - 1), DATE);
|
|
133
153
|
const short = FodId.tryParse(shortPayload);
|
|
134
154
|
console.log(' a payload one byte short -> ok ' + short.ok +
|
|
135
155
|
', status ' + short.status);
|
package/fodId.js
CHANGED
|
@@ -21,7 +21,10 @@
|
|
|
21
21
|
* ********************************************************************* */
|
|
22
22
|
|
|
23
23
|
const owid = require('owid');
|
|
24
|
+
const layout = require('./internal/layout');
|
|
24
25
|
const IdType = require('./idType');
|
|
26
|
+
const Usage = require('./usage');
|
|
27
|
+
const Terms = require('./internal/terms');
|
|
25
28
|
const FodIdParseError = require('./fodIdParseError');
|
|
26
29
|
|
|
27
30
|
/**
|
|
@@ -44,7 +47,15 @@ const ParseStatus = Object.freeze(Object.assign({}, owid.ParseStatus, {
|
|
|
44
47
|
* the match key that type carries after the header (16 GUID bytes for
|
|
45
48
|
* Random, 32 hash bytes for Probabilistic and HashedEmail).
|
|
46
49
|
*/
|
|
47
|
-
INVALID_TYPE_PAYLOAD_LENGTH: 'InvalidTypePayloadLength'
|
|
50
|
+
INVALID_TYPE_PAYLOAD_LENGTH: 'InvalidTypePayloadLength',
|
|
51
|
+
/**
|
|
52
|
+
* Bits 4 and 5 of the flags byte name a payload layout version this
|
|
53
|
+
* package does not know, so no field is read. A later version exists
|
|
54
|
+
* precisely because a field moved, so reading the payload under the
|
|
55
|
+
* layout this package knows would answer with values that are wrong
|
|
56
|
+
* rather than absent.
|
|
57
|
+
*/
|
|
58
|
+
UNSUPPORTED_PAYLOAD_VERSION: 'UnsupportedPayloadVersion'
|
|
48
59
|
}));
|
|
49
60
|
|
|
50
61
|
/**
|
|
@@ -82,42 +93,17 @@ const ParseStatus = Object.freeze(Object.assign({}, owid.ParseStatus, {
|
|
|
82
93
|
* A FodId composes the OWID the OWID library read (holds it and delegates
|
|
83
94
|
* the envelope fields to it). That OWID is frozen and hands out its byte
|
|
84
95
|
* arrays as copies, so nothing a caller holds can change the identifier.
|
|
96
|
+
*
|
|
97
|
+
* Every field has a named accessor here, so nothing needs the payload
|
|
98
|
+
* bytes or their offsets. The byte layout itself is specified once for all
|
|
99
|
+
* languages, and that specification is the authority rather than this
|
|
100
|
+
* comment:
|
|
101
|
+
* https://github.com/51Degrees/specifications/blob/main/did-specification/identifier-layout.md
|
|
102
|
+
*
|
|
103
|
+
* What each 51Did package offers on top of that layout is specified at:
|
|
104
|
+
* https://github.com/51Degrees/specifications/blob/main/did-specification/package-surface.md
|
|
85
105
|
*/
|
|
86
106
|
class FodId {
|
|
87
|
-
static FLAGS_OFFSET = 0;
|
|
88
|
-
static LICENSE_ID_OFFSET = 1;
|
|
89
|
-
static LICENSE_ID_LENGTH = 4;
|
|
90
|
-
/** Byte offset of the match key field within the payload. */
|
|
91
|
-
static MATCH_KEY_OFFSET = 5;
|
|
92
|
-
/**
|
|
93
|
-
* Byte length of the match key field for Probabilistic and HashedEmail
|
|
94
|
-
* identifiers, being a SHA-256.
|
|
95
|
-
*/
|
|
96
|
-
static MATCH_KEY_LENGTH = 32;
|
|
97
|
-
/**
|
|
98
|
-
* Deprecated alias for {@link FodId.MATCH_KEY_OFFSET}. The stable,
|
|
99
|
-
* comparable part of a 51Did is now called the match key, mirroring the
|
|
100
|
-
* Model Terms for Marketing vocabulary. This alias will be removed in a
|
|
101
|
-
* future release.
|
|
102
|
-
* @deprecated Renamed to MATCH_KEY_OFFSET. This alias will be removed in
|
|
103
|
-
* a future release.
|
|
104
|
-
*/
|
|
105
|
-
static HASH_OFFSET = FodId.MATCH_KEY_OFFSET;
|
|
106
|
-
/**
|
|
107
|
-
* Deprecated alias for {@link FodId.MATCH_KEY_LENGTH}. The stable,
|
|
108
|
-
* comparable part of a 51Did is now called the match key, mirroring the
|
|
109
|
-
* Model Terms for Marketing vocabulary. This alias will be removed in a
|
|
110
|
-
* future release.
|
|
111
|
-
* @deprecated Renamed to MATCH_KEY_LENGTH. This alias will be removed in
|
|
112
|
-
* a future release.
|
|
113
|
-
*/
|
|
114
|
-
static HASH_LENGTH = FodId.MATCH_KEY_LENGTH;
|
|
115
|
-
static HEADER_LENGTH = FodId.MATCH_KEY_OFFSET;
|
|
116
|
-
/** Byte length of the GUID match key carried by Random identifiers. */
|
|
117
|
-
static GUID_LENGTH = 16;
|
|
118
|
-
static RANDOM_PAYLOAD_LENGTH = 21;
|
|
119
|
-
static PAYLOAD_LENGTH = FodId.MATCH_KEY_OFFSET + FodId.MATCH_KEY_LENGTH;
|
|
120
|
-
|
|
121
107
|
/**
|
|
122
108
|
* Why a read succeeded or failed, being the OWID library's statuses plus
|
|
123
109
|
* `PAYLOAD_TOO_SHORT` and `INVALID_TYPE_PAYLOAD_LENGTH`. Frozen.
|
|
@@ -167,6 +153,8 @@ class FodId {
|
|
|
167
153
|
this._licenseId = read.value._licenseId;
|
|
168
154
|
/** @type {Uint8Array} this identifier's own copy of the match key bytes */
|
|
169
155
|
this._matchKey = read.value._matchKey;
|
|
156
|
+
/** @type {number} the terms index, zero where the payload carries none */
|
|
157
|
+
this._termsIndex = read.value._termsIndex;
|
|
170
158
|
}
|
|
171
159
|
|
|
172
160
|
/**
|
|
@@ -303,16 +291,31 @@ class FodId {
|
|
|
303
291
|
return new FodId(owidInstance);
|
|
304
292
|
}
|
|
305
293
|
|
|
306
|
-
/** @returns {number} the 1-byte usage flags bit-mask (0-255). */
|
|
307
|
-
get flags () {
|
|
308
|
-
return this._flags;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
294
|
/** @returns {number} the IdType carried in bits 6-7 of the flags. */
|
|
312
295
|
get type () {
|
|
313
296
|
return IdType.fromFlags(this._flags);
|
|
314
297
|
}
|
|
315
298
|
|
|
299
|
+
/**
|
|
300
|
+
* The Usage carried in bits 0-2 of the flags, as the highest usage
|
|
301
|
+
* granted. See Usage for why it is read that way.
|
|
302
|
+
* @returns {number} a Usage value
|
|
303
|
+
*/
|
|
304
|
+
get usage () {
|
|
305
|
+
return Usage.fromFlags(this._flags);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Whether the usage was derived from an IAB consent string the caller
|
|
310
|
+
* sent, rather than stated by the caller directly. Bit 3 of the flags.
|
|
311
|
+
* Both are legitimate ways to arrive at a usage, and this says nothing
|
|
312
|
+
* about which usage it is.
|
|
313
|
+
* @returns {boolean}
|
|
314
|
+
*/
|
|
315
|
+
get usageFromConsent () {
|
|
316
|
+
return (this._flags & 0b1000) !== 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
316
319
|
/**
|
|
317
320
|
* The 4-byte little-endian field at offset 1 of the payload, as an
|
|
318
321
|
* unsigned integer (0-4294967295).
|
|
@@ -342,16 +345,26 @@ class FodId {
|
|
|
342
345
|
}
|
|
343
346
|
|
|
344
347
|
/**
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
348
|
+
* The address of the terms document this 51Did was created under, read
|
|
349
|
+
* from the byte after the match key. The byte is an index into a table
|
|
350
|
+
* in the specification and this package turns the index into the
|
|
351
|
+
* address, so a caller never handles the byte. Nothing here fetches the
|
|
352
|
+
* address, because what to do with the document is the caller's
|
|
353
|
+
* decision.
|
|
354
|
+
*
|
|
355
|
+
* Null covers both an index of zero, which says the terms are not
|
|
356
|
+
* stated in the identifier, and an index added to the table after this
|
|
357
|
+
* package was released, which it cannot name. A caller cannot tell
|
|
358
|
+
* those two apart, which is deliberate, because both lead to the same
|
|
359
|
+
* place, being that the identifier does not say which terms it was
|
|
360
|
+
* created under and the answer has to come from somewhere else. No
|
|
361
|
+
* address is ever built from an index, since that would name a document
|
|
362
|
+
* nobody wrote.
|
|
363
|
+
* @returns {string|null} the address, or null where the identifier names
|
|
364
|
+
* no document this package knows, which is never an empty string
|
|
352
365
|
*/
|
|
353
|
-
get
|
|
354
|
-
return this.
|
|
366
|
+
get terms () {
|
|
367
|
+
return Terms.url(Terms.fromIndex(this._termsIndex));
|
|
355
368
|
}
|
|
356
369
|
|
|
357
370
|
/** @returns {number} the OWID version. */
|
|
@@ -365,24 +378,13 @@ class FodId {
|
|
|
365
378
|
}
|
|
366
379
|
|
|
367
380
|
/**
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
return this._owid.date;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* The envelope's own date as the unsigned 32-bit count of minutes since
|
|
377
|
-
* 2020-01-01T00:00:00Z, exactly as the wire carries it. This is the value
|
|
378
|
-
* the OWID `public-key?date=` parameter takes, and the integer to use when
|
|
379
|
-
* comparing creation times. The OWID library now reads the field unsigned
|
|
380
|
-
* too, so {@link FodId#date} agrees with this getter. The getter is kept
|
|
381
|
-
* because callers were told to use it, and it still forces the unsigned
|
|
382
|
-
* reading should the field ever arrive signed.
|
|
381
|
+
* The envelope's own date, being the count of minutes since
|
|
382
|
+
* 2020-01-01T00:00:00Z exactly as the wire carries it, read as an
|
|
383
|
+
* unsigned 32-bit number. This is the integer to compare when asking
|
|
384
|
+
* which of two identifiers was issued first.
|
|
383
385
|
* @returns {number} minutes since 2020-01-01T00:00:00Z
|
|
384
386
|
*/
|
|
385
|
-
get
|
|
387
|
+
get date () {
|
|
386
388
|
return this._owid.date >>> 0;
|
|
387
389
|
}
|
|
388
390
|
|
|
@@ -453,45 +455,62 @@ class FodId {
|
|
|
453
455
|
* Reads the 51Did fields out of an envelope payload, answering with a
|
|
454
456
|
* status rather than throwing. This is the one walk of the payload, shared
|
|
455
457
|
* by every surface that reads a 51Did. The type is read from the header and
|
|
456
|
-
* decides the least the payload must hold after the header.
|
|
457
|
-
* the match key
|
|
458
|
-
* cloud, so a longer payload is
|
|
458
|
+
* decides the least the payload must hold after the header. The terms byte
|
|
459
|
+
* follows the match key, and anything beyond the terms byte is a creator
|
|
460
|
+
* context section whose lengths belong to the cloud, so a longer payload is
|
|
461
|
+
* accepted whatever its length.
|
|
459
462
|
* @param {Uint8Array} payload the payload bytes
|
|
460
463
|
* @returns {{status: string, flags?: number, licenseId?: number,
|
|
461
|
-
* matchKey?: Uint8Array,
|
|
462
|
-
*
|
|
463
|
-
* type
|
|
464
|
+
* matchKey?: Uint8Array, termsIndex?: number, length: number,
|
|
465
|
+
* required: number, type?: number, payloadVersion?: number}} `status`
|
|
466
|
+
* PARSED with the fields, or a 51Did status with the length the type
|
|
467
|
+
* needed, and the version found where that is what the payload was refused
|
|
468
|
+
* for
|
|
464
469
|
*/
|
|
465
470
|
function unpack (payload) {
|
|
466
471
|
const length = payload.length;
|
|
467
|
-
if (length <
|
|
472
|
+
if (length < layout.HEADER_LENGTH) {
|
|
468
473
|
return {
|
|
469
474
|
status: ParseStatus.PAYLOAD_TOO_SHORT,
|
|
470
475
|
length,
|
|
471
|
-
required:
|
|
476
|
+
required: layout.HEADER_LENGTH
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
const flags = payload[layout.FLAGS_OFFSET];
|
|
480
|
+
// The version is read before any field, because a later version exists
|
|
481
|
+
// precisely because a field moved. Reading a payload of a version this
|
|
482
|
+
// package does not know under the layout it does know would answer with
|
|
483
|
+
// values that are wrong rather than absent, which is worse than
|
|
484
|
+
// refusing, and a version that nothing checks protects nothing.
|
|
485
|
+
const payloadVersion = (flags >> 4) & 0b11;
|
|
486
|
+
if (payloadVersion !== layout.SUPPORTED_PAYLOAD_VERSION) {
|
|
487
|
+
return {
|
|
488
|
+
status: ParseStatus.UNSUPPORTED_PAYLOAD_VERSION,
|
|
489
|
+
length,
|
|
490
|
+
required: layout.HEADER_LENGTH,
|
|
491
|
+
payloadVersion
|
|
472
492
|
};
|
|
473
493
|
}
|
|
474
|
-
const flags = payload[FodId.FLAGS_OFFSET];
|
|
475
494
|
// Little-endian unsigned 32-bit. `>>> 0` forces unsigned so the high bit
|
|
476
495
|
// does not produce a negative number.
|
|
477
496
|
const licenseId = (
|
|
478
|
-
payload[
|
|
479
|
-
(payload[
|
|
480
|
-
(payload[
|
|
481
|
-
(payload[
|
|
497
|
+
payload[layout.LICENSE_ID_OFFSET] |
|
|
498
|
+
(payload[layout.LICENSE_ID_OFFSET + 1] << 8) |
|
|
499
|
+
(payload[layout.LICENSE_ID_OFFSET + 2] << 16) |
|
|
500
|
+
(payload[layout.LICENSE_ID_OFFSET + 3] << 24)
|
|
482
501
|
) >>> 0;
|
|
483
502
|
const type = IdType.fromFlags(flags);
|
|
484
503
|
let matchKeyLength;
|
|
485
504
|
if (type === IdType.RANDOM) {
|
|
486
|
-
matchKeyLength =
|
|
505
|
+
matchKeyLength = layout.GUID_LENGTH;
|
|
487
506
|
} else if (type === IdType.RESERVED) {
|
|
488
507
|
// Not yet assigned, so read best-effort, whatever follows the header
|
|
489
508
|
// is the match key.
|
|
490
|
-
matchKeyLength = length -
|
|
509
|
+
matchKeyLength = length - layout.HEADER_LENGTH;
|
|
491
510
|
} else {
|
|
492
|
-
matchKeyLength =
|
|
511
|
+
matchKeyLength = layout.MATCH_KEY_LENGTH;
|
|
493
512
|
}
|
|
494
|
-
const required =
|
|
513
|
+
const required = layout.HEADER_LENGTH + matchKeyLength;
|
|
495
514
|
if (length < required) {
|
|
496
515
|
return {
|
|
497
516
|
status: ParseStatus.INVALID_TYPE_PAYLOAD_LENGTH,
|
|
@@ -500,13 +519,28 @@ function unpack (payload) {
|
|
|
500
519
|
type
|
|
501
520
|
};
|
|
502
521
|
}
|
|
522
|
+
// The terms byte sits after the match key, so where it sits follows the
|
|
523
|
+
// match key length the type selects. A payload with no byte to read is a
|
|
524
|
+
// terms index of zero, which says the terms are not stated, so absence
|
|
525
|
+
// and zero are the same answer and neither has to be told from the
|
|
526
|
+
// other.
|
|
527
|
+
//
|
|
528
|
+
// A Reserved type cannot carry a terms byte this reader can find, because
|
|
529
|
+
// the match key length for that type is not defined and every byte after
|
|
530
|
+
// the header is therefore the match key. Such an identifier reads as a
|
|
531
|
+
// terms index of zero, which is correct and is not a missing case here.
|
|
532
|
+
const termsOffset = layout.MATCH_KEY_OFFSET + matchKeyLength;
|
|
533
|
+
const termsIndex = termsOffset + layout.TERMS_LENGTH <= length
|
|
534
|
+
? payload[termsOffset]
|
|
535
|
+
: Terms.NOT_STATED;
|
|
503
536
|
return {
|
|
504
537
|
status: ParseStatus.PARSED,
|
|
505
538
|
flags,
|
|
506
539
|
licenseId,
|
|
507
540
|
// slice() copies, so the stored match key is this identifier's own.
|
|
508
541
|
matchKey: payload.slice(
|
|
509
|
-
|
|
542
|
+
layout.MATCH_KEY_OFFSET, layout.MATCH_KEY_OFFSET + matchKeyLength),
|
|
543
|
+
termsIndex,
|
|
510
544
|
length,
|
|
511
545
|
required
|
|
512
546
|
};
|
|
@@ -537,6 +571,7 @@ function readEnvelope (read) {
|
|
|
537
571
|
fodId._flags = unpacked.flags;
|
|
538
572
|
fodId._licenseId = unpacked.licenseId;
|
|
539
573
|
fodId._matchKey = unpacked.matchKey;
|
|
574
|
+
fodId._termsIndex = unpacked.termsIndex;
|
|
540
575
|
return { ok: true, value: fodId, status: ParseStatus.PARSED };
|
|
541
576
|
}
|
|
542
577
|
|
|
@@ -581,7 +616,7 @@ function valueOrThrow (read) {
|
|
|
581
616
|
}
|
|
582
617
|
|
|
583
618
|
/**
|
|
584
|
-
* The exception for a failed read. The
|
|
619
|
+
* The exception for a failed read. The three 51Did payload statuses keep the
|
|
585
620
|
* RangeError this package has always thrown for them, and every OWID status
|
|
586
621
|
* is a FodIdParseError carrying the status. Each error carries `status` so
|
|
587
622
|
* the reason can be acted on without reading the message.
|
|
@@ -599,6 +634,10 @@ function errorFor (read) {
|
|
|
599
634
|
`51Did payload for the ${IdType.name(read.detail.type)} type must be ` +
|
|
600
635
|
`at least ${read.detail.required} bytes, and ${read.detail.length} ` +
|
|
601
636
|
'were given.');
|
|
637
|
+
} else if (read.status === ParseStatus.UNSUPPORTED_PAYLOAD_VERSION) {
|
|
638
|
+
error = new RangeError(
|
|
639
|
+
`51Did payload version ${read.detail.payloadVersion} is not one this ` +
|
|
640
|
+
'package can read.');
|
|
602
641
|
} else {
|
|
603
642
|
return new FodIdParseError(read.status);
|
|
604
643
|
}
|
package/fodIdParseError.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* constructor) when the OWID library refused the envelope. The status names
|
|
27
27
|
* the reason in the same vocabulary the non-throwing surfaces report, so a
|
|
28
28
|
* caller catching this can act on the reason without reading the message.
|
|
29
|
-
* The
|
|
29
|
+
* The three 51Did payload statuses are thrown as RangeError instead, as this
|
|
30
30
|
* package has always thrown them, and that RangeError carries `status` too.
|
|
31
31
|
*/
|
|
32
32
|
class FodIdParseError extends Error {
|
package/index.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
const FodId = require('./fodId');
|
|
24
24
|
const FodIdParseError = require('./fodIdParseError');
|
|
25
25
|
const IdType = require('./idType');
|
|
26
|
+
const Usage = require('./usage');
|
|
26
27
|
const {
|
|
27
28
|
DidClient,
|
|
28
29
|
RedeemResult,
|
|
@@ -39,6 +40,7 @@ module.exports = {
|
|
|
39
40
|
FodId,
|
|
40
41
|
FodIdParseError,
|
|
41
42
|
IdType,
|
|
43
|
+
Usage,
|
|
42
44
|
DidClient,
|
|
43
45
|
RedeemResult,
|
|
44
46
|
ContextResult,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/* *********************************************************************
|
|
2
|
+
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
|
|
3
|
+
* Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
|
|
4
|
+
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
|
|
5
|
+
*
|
|
6
|
+
* This Original Work is licensed under the European Union Public Licence
|
|
7
|
+
* (EUPL) v.1.2 and is subject to its terms as set out below.
|
|
8
|
+
*
|
|
9
|
+
* If a copy of the EUPL was not distributed with this file, You can obtain
|
|
10
|
+
* one at https://opensource.org/licenses/EUPL-1.2.
|
|
11
|
+
*
|
|
12
|
+
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
|
|
13
|
+
* amended by the European Commission) shall be deemed incompatible for
|
|
14
|
+
* the purposes of the Work and the provisions of the compatibility
|
|
15
|
+
* clause in Article 5 of the EUPL shall not apply.
|
|
16
|
+
*
|
|
17
|
+
* If using the Work as, or as part of, a network application, by
|
|
18
|
+
* including the attribution notice(s) required under Article 5 of the EUPL
|
|
19
|
+
* in the end user terms of the application under an appropriate heading,
|
|
20
|
+
* such notice(s) shall fulfill the requirements of that article.
|
|
21
|
+
* ********************************************************************* */
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The byte layout of a 51Did payload. Internal to this package, because the
|
|
25
|
+
* only reason to read the payload by hand is to read a field that already
|
|
26
|
+
* has a name on FodId, and reading the flags byte by hand is how the usage
|
|
27
|
+
* comes out backwards. The usage bits are cumulative, so a caller masking
|
|
28
|
+
* for the non-marketing bit alone reads every marketing identifier as
|
|
29
|
+
* non-marketing. Use the FodId accessors instead.
|
|
30
|
+
*
|
|
31
|
+
* The layout is described once for every language in the 51Did
|
|
32
|
+
* specification, which is the authority for these numbers:
|
|
33
|
+
* https://github.com/51Degrees/specifications/blob/main/did-specification/identifier-layout.md
|
|
34
|
+
*
|
|
35
|
+
* What each package offers on top of that layout is described in:
|
|
36
|
+
* https://github.com/51Degrees/specifications/blob/main/did-specification/package-surface.md
|
|
37
|
+
*/
|
|
38
|
+
module.exports = Object.freeze({
|
|
39
|
+
/** Byte offset of the flags byte within the payload. */
|
|
40
|
+
FLAGS_OFFSET: 0,
|
|
41
|
+
/** Byte offset of the licence id field within the payload. */
|
|
42
|
+
LICENSE_ID_OFFSET: 1,
|
|
43
|
+
/** Byte length of the licence id field. */
|
|
44
|
+
LICENSE_ID_LENGTH: 4,
|
|
45
|
+
/** Byte offset of the match key field within the payload. */
|
|
46
|
+
MATCH_KEY_OFFSET: 5,
|
|
47
|
+
/**
|
|
48
|
+
* Byte length of the match key field for Probabilistic and HashedEmail
|
|
49
|
+
* identifiers, being a SHA-256.
|
|
50
|
+
*/
|
|
51
|
+
MATCH_KEY_LENGTH: 32,
|
|
52
|
+
/**
|
|
53
|
+
* Byte length of the terms field, which follows the match key. Its
|
|
54
|
+
* offset is not a constant here, because the match key length depends on
|
|
55
|
+
* the identifier type, so the offset is worked out from the type. A
|
|
56
|
+
* payload that ends at the match key carries no terms byte and reads as
|
|
57
|
+
* a terms index of zero, so the least payload lengths below do not
|
|
58
|
+
* include it.
|
|
59
|
+
*/
|
|
60
|
+
TERMS_LENGTH: 1,
|
|
61
|
+
/** Byte length of the flags and licence id fields together. */
|
|
62
|
+
HEADER_LENGTH: 5,
|
|
63
|
+
/** Byte length of the GUID match key carried by Random identifiers. */
|
|
64
|
+
GUID_LENGTH: 16,
|
|
65
|
+
/**
|
|
66
|
+
* The payload layout version this package reads, carried in bits 4 and
|
|
67
|
+
* 5 of the flags byte. Any other version is refused rather than read
|
|
68
|
+
* under this layout.
|
|
69
|
+
*/
|
|
70
|
+
SUPPORTED_PAYLOAD_VERSION: 0,
|
|
71
|
+
/** Least payload length for a Random identifier. */
|
|
72
|
+
RANDOM_PAYLOAD_LENGTH: 21,
|
|
73
|
+
/**
|
|
74
|
+
* Least payload length for a Probabilistic or HashedEmail identifier.
|
|
75
|
+
*/
|
|
76
|
+
PAYLOAD_LENGTH: 37
|
|
77
|
+
});
|