Skip to content

Commit b646b67

Browse files
committed
feat: add carv2-messaging example for embedding messages in CARv2
1 parent 784f9a8 commit b646b67

File tree

1 file changed

+90
-0
lines changed

1 file changed

+90
-0
lines changed

examples/carv2-messaging.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env node
2+
3+
import fs from 'fs/promises'
4+
import { Readable } from 'stream'
5+
import { CarReader, CarWriter } from '@ipld/car'
6+
import * as raw from 'multiformats/codecs/raw'
7+
import * as dagCbor from '@ipld/dag-cbor'
8+
import { CID } from 'multiformats/cid'
9+
import { sha256 } from 'multiformats/hashes/sha2'
10+
import varint from 'varint'
11+
import bl from 'bl'
12+
13+
async function example () {
14+
/** WRITE **/
15+
{
16+
// create a CARv1 payload, just a single block
17+
const bytes = new TextEncoder().encode('random meaningless bytes')
18+
const hash = await sha256.digest(raw.encode(bytes))
19+
const cid = CID.create(1, raw.code, hash)
20+
const { writer, out } = await CarWriter.create([cid])
21+
const v1Payload = bl()
22+
Readable.from(out).pipe(v1Payload)
23+
await writer.put({ cid, bytes })
24+
await writer.close()
25+
26+
// _the_ CARv2 pragma, 11 bytes - a fake CARv1-like header that says {version:2}
27+
const v2Pragma = new Uint8Array([10, 161, 103, 118, 101, 114, 115, 105, 111, 110, 2])
28+
// sneaky message in CARv2 wrapper
29+
const msg = dagCbor.encode({ sneaky: 'sending a message outside of CARv1 payload', expectedRoot: cid })
30+
const msgLength = new Uint8Array(varint.encode(msg.byteLength))
31+
/* v2 header is 40 bytes - characteristics: 16, v1 offset: 8, v1 size: 8, index offset: 8 */
32+
const v2Data = new Uint8Array(v2Pragma.byteLength + 40 + msgLength.byteLength + msg.byteLength + v1Payload.length)
33+
v2Data.set(v2Pragma, 0)
34+
// characteristics are the first 16 bytes after the pragma, the leftmost bit means
35+
// "is fully indexed", so we're using the 2nd to leftmost bit set to indicate
36+
// "message after v2 header" (TODO: codify this)
37+
let off = v2Pragma.byteLength
38+
v2Data[off] = (1 << 6)
39+
const dv = new DataView(v2Data.buffer, v2Data.byteOffset, v2Data.byteLength)
40+
off += 16 // characteristics
41+
// where is the v1 payload?
42+
const dataOffset = v2Pragma.byteLength + 40 + msgLength.byteLength + msg.byteLength
43+
dv.setBigUint64(off, BigInt(dataOffset), true) // position 16 is "data offset"
44+
off += 8
45+
dv.setBigUint64(off, BigInt(v1Payload.length), true) // position 16+8 is "data size"
46+
off += 8
47+
// position 16+8+8 is "index offset", leaving it as zero says "no index"
48+
off += 8
49+
50+
v2Data.set(msgLength, off)
51+
off += msgLength.byteLength
52+
v2Data.set(msg, off)
53+
off += msg.byteLength
54+
v2Data.set(v1Payload.slice(), off)
55+
56+
await fs.writeFile('example-messaging.car', v2Data)
57+
}
58+
59+
/** READ **/
60+
{
61+
const inputBytes = await fs.readFile('example-messaging.car')
62+
// read and parse the entire stream in one go, this will cache the contents of
63+
// the car in memory so is not suitable for large files.
64+
const reader = await CarReader.fromBytes(inputBytes)
65+
66+
// read the CARv1 contents as normal
67+
const roots = await reader.getRoots()
68+
const got = await reader.get(roots[0])
69+
70+
console.log('Retrieved [%s] from example-messaging.car with CID [%s]',
71+
new TextDecoder().decode(got.bytes),
72+
roots[0].toString())
73+
74+
// BUT, our sneaky message is in here too
75+
console.log('Is CARv2?', reader._header.version === 2)
76+
console.log('Has message?', (reader._header.characteristics[0] & (1n << 6n)) !== 0)
77+
let off = /* pragma */ 11 + /* header */ 40
78+
const msgLength = varint.decode(inputBytes.slice(off))
79+
console.log('Message length:', msgLength)
80+
off += varint.decode.bytes
81+
const msgBytes = inputBytes.slice(off, off + msgLength)
82+
const msg = dagCbor.decode(msgBytes)
83+
console.log('Message:', msg)
84+
}
85+
}
86+
87+
example().catch((err) => {
88+
console.error(err)
89+
process.exit(1)
90+
})

0 commit comments

Comments
 (0)