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