+69
-1
src/pds.js
+69
-1
src/pds.js
···
376
376
}
377
377
}
378
378
379
+
// === CAR FILE BUILDER ===
380
+
381
+
function varint(n) {
382
+
const bytes = []
383
+
while (n >= 0x80) {
384
+
bytes.push((n & 0x7f) | 0x80)
385
+
n >>>= 7
386
+
}
387
+
bytes.push(n)
388
+
return new Uint8Array(bytes)
389
+
}
390
+
391
+
function cidToBytes(cidStr) {
392
+
// Decode base32lower CID string to bytes
393
+
if (!cidStr.startsWith('b')) throw new Error('expected base32lower CID')
394
+
return base32Decode(cidStr.slice(1))
395
+
}
396
+
397
+
function base32Decode(str) {
398
+
const alphabet = 'abcdefghijklmnopqrstuvwxyz234567'
399
+
let bits = 0
400
+
let value = 0
401
+
const output = []
402
+
403
+
for (const char of str) {
404
+
const idx = alphabet.indexOf(char)
405
+
if (idx === -1) continue
406
+
value = (value << 5) | idx
407
+
bits += 5
408
+
if (bits >= 8) {
409
+
bits -= 8
410
+
output.push((value >> bits) & 0xff)
411
+
}
412
+
}
413
+
414
+
return new Uint8Array(output)
415
+
}
416
+
417
+
function buildCarFile(rootCid, blocks) {
418
+
const parts = []
419
+
420
+
// Header: { version: 1, roots: [rootCid] }
421
+
const rootCidBytes = cidToBytes(rootCid)
422
+
const header = cborEncode({ version: 1, roots: [rootCidBytes] })
423
+
parts.push(varint(header.length))
424
+
parts.push(header)
425
+
426
+
// Blocks: varint(len) + cid + data
427
+
for (const block of blocks) {
428
+
const cidBytes = cidToBytes(block.cid)
429
+
const blockLen = cidBytes.length + block.data.length
430
+
parts.push(varint(blockLen))
431
+
parts.push(cidBytes)
432
+
parts.push(block.data)
433
+
}
434
+
435
+
// Concatenate all parts
436
+
const totalLen = parts.reduce((sum, p) => sum + p.length, 0)
437
+
const car = new Uint8Array(totalLen)
438
+
let offset = 0
439
+
for (const part of parts) {
440
+
car.set(part, offset)
441
+
offset += part.length
442
+
}
443
+
444
+
return car
445
+
}
446
+
379
447
export class PersonalDataServer {
380
448
constructor(state, env) {
381
449
this.state = state
···
635
703
export {
636
704
cborEncode, cborDecode, createCid, cidToString, base32Encode, createTid,
637
705
generateKeyPair, importPrivateKey, sign, bytesToHex, hexToBytes,
638
-
getKeyDepth
706
+
getKeyDepth, varint, base32Decode, buildCarFile
639
707
}
+36
-1
test/pds.test.js
+36
-1
test/pds.test.js
···
3
3
import {
4
4
cborEncode, cborDecode, createCid, cidToString, base32Encode, createTid,
5
5
generateKeyPair, importPrivateKey, sign, bytesToHex, hexToBytes,
6
-
getKeyDepth
6
+
getKeyDepth, varint, base32Decode, buildCarFile
7
7
} from '../src/pds.js'
8
8
9
9
describe('CBOR Encoding', () => {
···
265
265
assert.deepStrictEqual(decoded, original)
266
266
})
267
267
})
268
+
269
+
describe('CAR File Builder', () => {
270
+
test('varint encodes small numbers', () => {
271
+
assert.deepStrictEqual(varint(0), new Uint8Array([0]))
272
+
assert.deepStrictEqual(varint(1), new Uint8Array([1]))
273
+
assert.deepStrictEqual(varint(127), new Uint8Array([127]))
274
+
})
275
+
276
+
test('varint encodes multi-byte numbers', () => {
277
+
// 128 = 0x80 -> [0x80 | 0x00, 0x01] = [0x80, 0x01]
278
+
assert.deepStrictEqual(varint(128), new Uint8Array([0x80, 0x01]))
279
+
// 300 = 0x12c -> [0xac, 0x02]
280
+
assert.deepStrictEqual(varint(300), new Uint8Array([0xac, 0x02]))
281
+
})
282
+
283
+
test('base32 encode/decode roundtrip', () => {
284
+
const original = new Uint8Array([0x01, 0x71, 0x12, 0x20, 0xab, 0xcd])
285
+
const encoded = base32Encode(original)
286
+
const decoded = base32Decode(encoded)
287
+
assert.deepStrictEqual(decoded, original)
288
+
})
289
+
290
+
test('buildCarFile produces valid structure', async () => {
291
+
const data = cborEncode({ test: 'data' })
292
+
const cid = await createCid(data)
293
+
const cidStr = cidToString(cid)
294
+
295
+
const car = buildCarFile(cidStr, [{ cid: cidStr, data }])
296
+
297
+
assert.ok(car instanceof Uint8Array)
298
+
assert.ok(car.length > 0)
299
+
// First byte should be varint of header length
300
+
assert.ok(car[0] > 0)
301
+
})
302
+
})