+44
-67
src/pds.js
+44
-67
src/pds.js
···
22
// === CBOR ENCODING ===
23
// Minimal deterministic CBOR (RFC 8949) - sorted keys, minimal integers
24
25
export function cborEncode(value) {
26
const parts = []
27
···
36
encodeInteger(val)
37
} else if (typeof val === 'string') {
38
const bytes = new TextEncoder().encode(val)
39
-
encodeHead(3, bytes.length) // major type 3 = text string
40
parts.push(...bytes)
41
} else if (val instanceof Uint8Array) {
42
-
encodeHead(2, val.length) // major type 2 = byte string
43
parts.push(...val)
44
} else if (Array.isArray(val)) {
45
-
encodeHead(4, val.length) // major type 4 = array
46
for (const item of val) encode(item)
47
} else if (typeof val === 'object') {
48
// Sort keys for deterministic encoding
49
const keys = Object.keys(val).sort()
50
-
encodeHead(5, keys.length) // major type 5 = map
51
for (const key of keys) {
52
encode(key)
53
encode(val[key])
···
55
}
56
}
57
58
-
function encodeHead(majorType, length) {
59
-
const mt = majorType << 5
60
-
if (length < 24) {
61
-
parts.push(mt | length)
62
-
} else if (length < 256) {
63
-
parts.push(mt | 24, length)
64
-
} else if (length < 65536) {
65
-
parts.push(mt | 25, length >> 8, length & 0xff)
66
-
} else if (length < 4294967296) {
67
-
// Use Math.floor instead of bitshift to avoid 32-bit signed integer overflow
68
-
parts.push(mt | 26,
69
-
Math.floor(length / 0x1000000) & 0xff,
70
-
Math.floor(length / 0x10000) & 0xff,
71
-
Math.floor(length / 0x100) & 0xff,
72
-
length & 0xff)
73
-
}
74
-
}
75
-
76
function encodeInteger(n) {
77
if (n >= 0) {
78
-
encodeHead(0, n) // major type 0 = unsigned int
79
} else {
80
-
encodeHead(1, -n - 1) // major type 1 = negative int
81
}
82
}
83
···
98
parts.push(CBOR_FALSE)
99
} else if (typeof val === 'number') {
100
if (Number.isInteger(val) && val >= 0) {
101
-
encodeHead(0, val)
102
} else if (Number.isInteger(val) && val < 0) {
103
-
encodeHead(1, -val - 1)
104
}
105
} else if (typeof val === 'string') {
106
const bytes = new TextEncoder().encode(val)
107
-
encodeHead(3, bytes.length)
108
parts.push(...bytes)
109
} else if (val instanceof CID) {
110
// CID - encode with CBOR tag 42 + 0x00 prefix
111
parts.push(0xd8, CBOR_TAG_CID)
112
-
encodeHead(2, val.bytes.length + 1) // +1 for 0x00 prefix
113
parts.push(0x00) // multibase identity prefix
114
parts.push(...val.bytes)
115
} else if (val instanceof Uint8Array) {
116
// Regular byte string
117
-
encodeHead(2, val.length)
118
parts.push(...val)
119
} else if (Array.isArray(val)) {
120
-
encodeHead(4, val.length)
121
for (const item of val) encode(item)
122
} else if (typeof val === 'object') {
123
// DAG-CBOR: sort keys by length first, then lexicographically
···
126
if (a.length !== b.length) return a.length - b.length
127
return a < b ? -1 : a > b ? 1 : 0
128
})
129
-
encodeHead(5, keys.length)
130
for (const key of keys) {
131
const keyBytes = new TextEncoder().encode(key)
132
-
encodeHead(3, keyBytes.length)
133
parts.push(...keyBytes)
134
encode(val[key])
135
}
136
-
}
137
-
}
138
-
139
-
function encodeHead(majorType, length) {
140
-
const mt = majorType << 5
141
-
if (length < 24) {
142
-
parts.push(mt | length)
143
-
} else if (length < 256) {
144
-
parts.push(mt | 24, length)
145
-
} else if (length < 65536) {
146
-
parts.push(mt | 25, length >> 8, length & 0xff)
147
-
} else if (length < 4294967296) {
148
-
// Use Math.floor instead of bitshift to avoid 32-bit signed integer overflow
149
-
parts.push(mt | 26,
150
-
Math.floor(length / 0x1000000) & 0xff,
151
-
Math.floor(length / 0x10000) & 0xff,
152
-
Math.floor(length / 0x100) & 0xff,
153
-
length & 0xff)
154
}
155
}
156
···
580
if (val === null || val === undefined) {
581
parts.push(CBOR_NULL)
582
} else if (typeof val === 'number') {
583
-
encodeHead(0, val) // unsigned int
584
} else if (val instanceof CID) {
585
// CID - encode with CBOR tag 42 + 0x00 prefix (DAG-CBOR CID link)
586
parts.push(0xd8, CBOR_TAG_CID)
587
-
encodeHead(2, val.bytes.length + 1) // +1 for 0x00 prefix
588
parts.push(0x00) // multibase identity prefix
589
parts.push(...val.bytes)
590
} else if (val instanceof Uint8Array) {
591
// Regular bytes
592
-
encodeHead(2, val.length)
593
parts.push(...val)
594
} else if (Array.isArray(val)) {
595
-
encodeHead(4, val.length)
596
for (const item of val) encode(item)
597
} else if (typeof val === 'object') {
598
// Sort keys for deterministic encoding (DAG-CBOR style)
···
603
if (a.length !== b.length) return a.length - b.length
604
return a < b ? -1 : a > b ? 1 : 0
605
})
606
-
encodeHead(5, keys.length)
607
for (const key of keys) {
608
// Encode key as text string
609
const keyBytes = new TextEncoder().encode(key)
610
-
encodeHead(3, keyBytes.length)
611
parts.push(...keyBytes)
612
// Encode value
613
encode(val[key])
614
}
615
-
}
616
-
}
617
-
618
-
function encodeHead(majorType, length) {
619
-
const mt = majorType << 5
620
-
if (length < 24) {
621
-
parts.push(mt | length)
622
-
} else if (length < 256) {
623
-
parts.push(mt | 24, length)
624
-
} else if (length < 65536) {
625
-
parts.push(mt | 25, length >> 8, length & 0xff)
626
}
627
}
628
···
22
// === CBOR ENCODING ===
23
// Minimal deterministic CBOR (RFC 8949) - sorted keys, minimal integers
24
25
+
/**
26
+
* Encode CBOR type header (major type + length)
27
+
* @param {number[]} parts - Array to push bytes to
28
+
* @param {number} majorType - CBOR major type (0-7)
29
+
* @param {number} length - Value or length to encode
30
+
*/
31
+
function encodeHead(parts, majorType, length) {
32
+
const mt = majorType << 5
33
+
if (length < 24) {
34
+
parts.push(mt | length)
35
+
} else if (length < 256) {
36
+
parts.push(mt | 24, length)
37
+
} else if (length < 65536) {
38
+
parts.push(mt | 25, length >> 8, length & 0xff)
39
+
} else if (length < 4294967296) {
40
+
// Use Math.floor instead of bitshift to avoid 32-bit signed integer overflow
41
+
parts.push(mt | 26,
42
+
Math.floor(length / 0x1000000) & 0xff,
43
+
Math.floor(length / 0x10000) & 0xff,
44
+
Math.floor(length / 0x100) & 0xff,
45
+
length & 0xff)
46
+
}
47
+
}
48
+
49
export function cborEncode(value) {
50
const parts = []
51
···
60
encodeInteger(val)
61
} else if (typeof val === 'string') {
62
const bytes = new TextEncoder().encode(val)
63
+
encodeHead(parts, 3, bytes.length) // major type 3 = text string
64
parts.push(...bytes)
65
} else if (val instanceof Uint8Array) {
66
+
encodeHead(parts, 2, val.length) // major type 2 = byte string
67
parts.push(...val)
68
} else if (Array.isArray(val)) {
69
+
encodeHead(parts, 4, val.length) // major type 4 = array
70
for (const item of val) encode(item)
71
} else if (typeof val === 'object') {
72
// Sort keys for deterministic encoding
73
const keys = Object.keys(val).sort()
74
+
encodeHead(parts, 5, keys.length) // major type 5 = map
75
for (const key of keys) {
76
encode(key)
77
encode(val[key])
···
79
}
80
}
81
82
function encodeInteger(n) {
83
if (n >= 0) {
84
+
encodeHead(parts, 0, n) // major type 0 = unsigned int
85
} else {
86
+
encodeHead(parts, 1, -n - 1) // major type 1 = negative int
87
}
88
}
89
···
104
parts.push(CBOR_FALSE)
105
} else if (typeof val === 'number') {
106
if (Number.isInteger(val) && val >= 0) {
107
+
encodeHead(parts, 0, val)
108
} else if (Number.isInteger(val) && val < 0) {
109
+
encodeHead(parts, 1, -val - 1)
110
}
111
} else if (typeof val === 'string') {
112
const bytes = new TextEncoder().encode(val)
113
+
encodeHead(parts, 3, bytes.length)
114
parts.push(...bytes)
115
} else if (val instanceof CID) {
116
// CID - encode with CBOR tag 42 + 0x00 prefix
117
parts.push(0xd8, CBOR_TAG_CID)
118
+
encodeHead(parts, 2, val.bytes.length + 1) // +1 for 0x00 prefix
119
parts.push(0x00) // multibase identity prefix
120
parts.push(...val.bytes)
121
} else if (val instanceof Uint8Array) {
122
// Regular byte string
123
+
encodeHead(parts, 2, val.length)
124
parts.push(...val)
125
} else if (Array.isArray(val)) {
126
+
encodeHead(parts, 4, val.length)
127
for (const item of val) encode(item)
128
} else if (typeof val === 'object') {
129
// DAG-CBOR: sort keys by length first, then lexicographically
···
132
if (a.length !== b.length) return a.length - b.length
133
return a < b ? -1 : a > b ? 1 : 0
134
})
135
+
encodeHead(parts, 5, keys.length)
136
for (const key of keys) {
137
const keyBytes = new TextEncoder().encode(key)
138
+
encodeHead(parts, 3, keyBytes.length)
139
parts.push(...keyBytes)
140
encode(val[key])
141
}
142
}
143
}
144
···
568
if (val === null || val === undefined) {
569
parts.push(CBOR_NULL)
570
} else if (typeof val === 'number') {
571
+
encodeHead(parts, 0, val) // unsigned int
572
} else if (val instanceof CID) {
573
// CID - encode with CBOR tag 42 + 0x00 prefix (DAG-CBOR CID link)
574
parts.push(0xd8, CBOR_TAG_CID)
575
+
encodeHead(parts, 2, val.bytes.length + 1) // +1 for 0x00 prefix
576
parts.push(0x00) // multibase identity prefix
577
parts.push(...val.bytes)
578
} else if (val instanceof Uint8Array) {
579
// Regular bytes
580
+
encodeHead(parts, 2, val.length)
581
parts.push(...val)
582
} else if (Array.isArray(val)) {
583
+
encodeHead(parts, 4, val.length)
584
for (const item of val) encode(item)
585
} else if (typeof val === 'object') {
586
// Sort keys for deterministic encoding (DAG-CBOR style)
···
591
if (a.length !== b.length) return a.length - b.length
592
return a < b ? -1 : a > b ? 1 : 0
593
})
594
+
encodeHead(parts, 5, keys.length)
595
for (const key of keys) {
596
// Encode key as text string
597
const keyBytes = new TextEncoder().encode(key)
598
+
encodeHead(parts, 3, keyBytes.length)
599
parts.push(...keyBytes)
600
// Encode value
601
encode(val[key])
602
}
603
}
604
}
605