Skip to content

Commit ded7643

Browse files
committed
feat: allow encoding Data fields before Links in PBNode messages
To allow efficient reading of HAMT directory contents, encode Data messages before Links. This would be a breaking change as CIDs would change, so add an option to allow users to opt-in to this new behaviour. Refs: - ipld/ipld#383 - ipfs/specs#550
1 parent 3b870f6 commit ded7643

4 files changed

Lines changed: 67 additions & 8 deletions

File tree

src/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,18 @@ import { prepare, validate, createNode, createLink, toByteView } from './util.js
1616
/**
1717
* @typedef {import('./interface.js').PBLink} PBLink
1818
* @typedef {import('./interface.js').PBNode} PBNode
19+
* @typedef {import('./interface.js').EncodeOptions} EncodeOptions
1920
*/
2021

2122
export const name = 'dag-pb'
2223
export const code = 0x70
2324

2425
/**
2526
* @param {PBNode} node
27+
* @param {EncodeOptions} [options]
2628
* @returns {ByteView<PBNode>}
2729
*/
28-
export function encode (node) {
30+
export function encode (node, options) {
2931
validate(node)
3032

3133
const pbn = {}
@@ -48,7 +50,7 @@ export function encode (node) {
4850
pbn.Data = node.Data
4951
}
5052

51-
return encodeNode(pbn)
53+
return encodeNode(pbn, options)
5254
}
5355

5456
/**

src/interface.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,13 @@ export interface RawPBNode {
3131
Data: Uint8Array
3232
Links: RawPBLink[]
3333
}
34+
35+
export interface EncodeOptions {
36+
/**
37+
* By default the `Data` field in a `PBNode` message is encoded after all
38+
* repeated `Links` elements.
39+
*
40+
* Pass `true` here to encode the `Data` field before any `Links`.
41+
*/
42+
dataFirst?: boolean
43+
}

src/pb-encode.js

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ const maxUInt32 = 2 ** 31
1010
* @typedef {import('./interface.js').RawPBNode} RawPBNode
1111
*/
1212

13+
/**
14+
* @typedef {import('./interface.js').EncodeOptions} EncodeOptions
15+
*/
16+
1317
// the encoders work backward from the end of the bytes array
1418

1519
/**
@@ -52,22 +56,35 @@ function encodeLink (link, bytes) {
5256
return bytes.length - i
5357
}
5458

59+
/**
60+
* @param {number} i
61+
* @param {RawPBNode} node
62+
* @param {Uint8Array} bytes
63+
* @returns number
64+
*/
65+
function encodeData (i, node, bytes) {
66+
i -= node.Data.length
67+
bytes.set(node.Data, i)
68+
i = encodeVarint(bytes, i, node.Data.length) - 1
69+
bytes[i] = 0xa
70+
71+
return i
72+
}
73+
5574
/**
5675
* Encodes a PBNode into a new byte array of precisely the correct size
5776
*
5877
* @param {RawPBNode} node
78+
* @param {EncodeOptions} [options]
5979
* @returns {Uint8Array}
6080
*/
61-
export function encodeNode (node) {
81+
export function encodeNode (node, options) {
6282
const size = sizeNode(node)
6383
const bytes = new Uint8Array(size)
6484
let i = size
6585

66-
if (node.Data) {
67-
i -= node.Data.length
68-
bytes.set(node.Data, i)
69-
i = encodeVarint(bytes, i, node.Data.length) - 1
70-
bytes[i] = 0xa
86+
if (node.Data && options?.dataFirst !== true) {
87+
i = encodeData(i, node, bytes)
7188
}
7289

7390
if (node.Links) {
@@ -79,6 +96,10 @@ export function encodeNode (node) {
7996
}
8097
}
8198

99+
if (node.Data && options?.dataFirst === true) {
100+
i = encodeData(i, node, bytes)
101+
}
102+
82103
return bytes
83104
}
84105

test/test-basics.spec.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,32 @@ describe('Basics', () => {
8585
assert.containSubset(linkCidsToStrings(node.Links), linkCidsToStrings(links))
8686
})
8787

88+
it('encode a node with links, data first', () => {
89+
const dataBytes = '0a050001020304'
90+
const linksBytes = '122f0a2212207521fe19c374a97759226dc5c0c8e674e73950e81b211f7dd3b6b30883a08a51120968656c6c6f2e747874'
91+
92+
const node = {
93+
Data: Uint8Array.from([0, 1, 2, 3, 4]),
94+
Links: [{
95+
Name: 'hello.txt', Hash: CID.parse('QmWDtUQj38YLW8v3q4A6LwPn4vYKEbuKWpgSm6bjKW6Xfe')
96+
}]
97+
}
98+
const defaultResult = encode(node)
99+
const expectedDefaultBytes = `${linksBytes}${dataBytes}`
100+
assert.instanceOf(defaultResult, Uint8Array)
101+
assert.deepEqual(bytes.toHex(defaultResult), expectedDefaultBytes)
102+
103+
const dataFirstResult = encode(node, {
104+
dataFirst: true
105+
})
106+
const expectedDataFirstBytes = `${dataBytes}${linksBytes}`
107+
assert.instanceOf(dataFirstResult, Uint8Array)
108+
assert.deepEqual(bytes.toHex(dataFirstResult), expectedDataFirstBytes)
109+
110+
assert.deepEqual(decode(defaultResult), node)
111+
assert.deepEqual(decode(dataFirstResult), node)
112+
})
113+
88114
it('ignore invalid properties when preparing', () => {
89115
const prepared = prepare({ foo: 'bar' })
90116
assert.deepEqual(prepared, { Links: [] })

0 commit comments

Comments
 (0)