@snaha/swarm-id 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +431 -0
- package/dist/chunk/bmt.d.ts +17 -0
- package/dist/chunk/bmt.d.ts.map +1 -0
- package/dist/chunk/cac.d.ts +18 -0
- package/dist/chunk/cac.d.ts.map +1 -0
- package/dist/chunk/constants.d.ts +10 -0
- package/dist/chunk/constants.d.ts.map +1 -0
- package/dist/chunk/encrypted-cac.d.ts +48 -0
- package/dist/chunk/encrypted-cac.d.ts.map +1 -0
- package/dist/chunk/encryption.d.ts +86 -0
- package/dist/chunk/encryption.d.ts.map +1 -0
- package/dist/chunk/index.d.ts +6 -0
- package/dist/chunk/index.d.ts.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/proxy/act/act.d.ts +78 -0
- package/dist/proxy/act/act.d.ts.map +1 -0
- package/dist/proxy/act/crypto.d.ts +44 -0
- package/dist/proxy/act/crypto.d.ts.map +1 -0
- package/dist/proxy/act/grantee-list.d.ts +82 -0
- package/dist/proxy/act/grantee-list.d.ts.map +1 -0
- package/dist/proxy/act/history.d.ts +183 -0
- package/dist/proxy/act/history.d.ts.map +1 -0
- package/dist/proxy/act/index.d.ts +104 -0
- package/dist/proxy/act/index.d.ts.map +1 -0
- package/dist/proxy/chunking-encrypted.d.ts +14 -0
- package/dist/proxy/chunking-encrypted.d.ts.map +1 -0
- package/dist/proxy/chunking.d.ts +15 -0
- package/dist/proxy/chunking.d.ts.map +1 -0
- package/dist/proxy/download-data.d.ts +16 -0
- package/dist/proxy/download-data.d.ts.map +1 -0
- package/dist/proxy/feed-manifest.d.ts +62 -0
- package/dist/proxy/feed-manifest.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts +77 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts +88 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/finder.d.ts +67 -0
- package/dist/proxy/feeds/epochs/finder.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/index.d.ts +35 -0
- package/dist/proxy/feeds/epochs/index.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/test-utils.d.ts +93 -0
- package/dist/proxy/feeds/epochs/test-utils.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/types.d.ts +109 -0
- package/dist/proxy/feeds/epochs/types.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/updater.d.ts +68 -0
- package/dist/proxy/feeds/epochs/updater.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/utils.d.ts +22 -0
- package/dist/proxy/feeds/epochs/utils.d.ts.map +1 -0
- package/dist/proxy/feeds/index.d.ts +5 -0
- package/dist/proxy/feeds/index.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/finder.d.ts +17 -0
- package/dist/proxy/feeds/sequence/finder.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/index.d.ts +23 -0
- package/dist/proxy/feeds/sequence/index.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/types.d.ts +80 -0
- package/dist/proxy/feeds/sequence/types.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/updater.d.ts +26 -0
- package/dist/proxy/feeds/sequence/updater.d.ts.map +1 -0
- package/dist/proxy/index.d.ts +6 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/manifest-builder.d.ts +183 -0
- package/dist/proxy/manifest-builder.d.ts.map +1 -0
- package/dist/proxy/mantaray-encrypted.d.ts +27 -0
- package/dist/proxy/mantaray-encrypted.d.ts.map +1 -0
- package/dist/proxy/mantaray.d.ts +26 -0
- package/dist/proxy/mantaray.d.ts.map +1 -0
- package/dist/proxy/types.d.ts +29 -0
- package/dist/proxy/types.d.ts.map +1 -0
- package/dist/proxy/upload-data.d.ts +17 -0
- package/dist/proxy/upload-data.d.ts.map +1 -0
- package/dist/proxy/upload-encrypted-data.d.ts +103 -0
- package/dist/proxy/upload-encrypted-data.d.ts.map +1 -0
- package/dist/schemas.d.ts +240 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/storage/debounced-uploader.d.ts +62 -0
- package/dist/storage/debounced-uploader.d.ts.map +1 -0
- package/dist/storage/utilization-store.d.ts +108 -0
- package/dist/storage/utilization-store.d.ts.map +1 -0
- package/dist/swarm-id-auth.d.ts +74 -0
- package/dist/swarm-id-auth.d.ts.map +1 -0
- package/dist/swarm-id-auth.js +2 -0
- package/dist/swarm-id-auth.js.map +1 -0
- package/dist/swarm-id-client.d.ts +878 -0
- package/dist/swarm-id-client.d.ts.map +1 -0
- package/dist/swarm-id-client.js +2 -0
- package/dist/swarm-id-client.js.map +1 -0
- package/dist/swarm-id-proxy.d.ts +236 -0
- package/dist/swarm-id-proxy.d.ts.map +1 -0
- package/dist/swarm-id-proxy.js +2 -0
- package/dist/swarm-id-proxy.js.map +1 -0
- package/dist/swarm-id.esm.js +2 -0
- package/dist/swarm-id.esm.js.map +1 -0
- package/dist/swarm-id.umd.js +2 -0
- package/dist/swarm-id.umd.js.map +1 -0
- package/dist/sync/index.d.ts +9 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/key-derivation.d.ts +25 -0
- package/dist/sync/key-derivation.d.ts.map +1 -0
- package/dist/sync/restore-account.d.ts +28 -0
- package/dist/sync/restore-account.d.ts.map +1 -0
- package/dist/sync/serialization.d.ts +16 -0
- package/dist/sync/serialization.d.ts.map +1 -0
- package/dist/sync/store-interfaces.d.ts +53 -0
- package/dist/sync/store-interfaces.d.ts.map +1 -0
- package/dist/sync/sync-account.d.ts +44 -0
- package/dist/sync/sync-account.d.ts.map +1 -0
- package/dist/sync/types.d.ts +13 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/test-fixtures.d.ts +17 -0
- package/dist/test-fixtures.d.ts.map +1 -0
- package/dist/types-BD_VkNn0.js +2 -0
- package/dist/types-BD_VkNn0.js.map +1 -0
- package/dist/types-lJCaT-50.js +2 -0
- package/dist/types-lJCaT-50.js.map +1 -0
- package/dist/types.d.ts +2157 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/account-payload.d.ts +94 -0
- package/dist/utils/account-payload.d.ts.map +1 -0
- package/dist/utils/account-state-snapshot.d.ts +38 -0
- package/dist/utils/account-state-snapshot.d.ts.map +1 -0
- package/dist/utils/backup-encryption.d.ts +127 -0
- package/dist/utils/backup-encryption.d.ts.map +1 -0
- package/dist/utils/batch-utilization.d.ts +432 -0
- package/dist/utils/batch-utilization.d.ts.map +1 -0
- package/dist/utils/constants.d.ts +11 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/hex.d.ts +17 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/key-derivation.d.ts +92 -0
- package/dist/utils/key-derivation.d.ts.map +1 -0
- package/dist/utils/storage-managers.d.ts +65 -0
- package/dist/utils/storage-managers.d.ts.map +1 -0
- package/dist/utils/swarm-id-export.d.ts +24 -0
- package/dist/utils/swarm-id-export.d.ts.map +1 -0
- package/dist/utils/ttl.d.ts +49 -0
- package/dist/utils/ttl.d.ts.map +1 -0
- package/dist/utils/url.d.ts +41 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/versioned-storage.d.ts +131 -0
- package/dist/utils/versioned-storage.d.ts.map +1 -0
- package/package.json +78 -0
- package/src/chunk/bmt.test.ts +217 -0
- package/src/chunk/bmt.ts +57 -0
- package/src/chunk/cac.test.ts +214 -0
- package/src/chunk/cac.ts +65 -0
- package/src/chunk/constants.ts +18 -0
- package/src/chunk/encrypted-cac.test.ts +385 -0
- package/src/chunk/encrypted-cac.ts +131 -0
- package/src/chunk/encryption.test.ts +352 -0
- package/src/chunk/encryption.ts +300 -0
- package/src/chunk/index.ts +47 -0
- package/src/index.ts +430 -0
- package/src/proxy/act/act.test.ts +278 -0
- package/src/proxy/act/act.ts +158 -0
- package/src/proxy/act/bee-compat.test.ts +948 -0
- package/src/proxy/act/crypto.test.ts +436 -0
- package/src/proxy/act/crypto.ts +376 -0
- package/src/proxy/act/grantee-list.test.ts +393 -0
- package/src/proxy/act/grantee-list.ts +239 -0
- package/src/proxy/act/history.test.ts +360 -0
- package/src/proxy/act/history.ts +413 -0
- package/src/proxy/act/index.test.ts +748 -0
- package/src/proxy/act/index.ts +853 -0
- package/src/proxy/chunking-encrypted.ts +95 -0
- package/src/proxy/chunking.ts +65 -0
- package/src/proxy/download-data.ts +448 -0
- package/src/proxy/feed-manifest.ts +174 -0
- package/src/proxy/feeds/epochs/async-finder.ts +372 -0
- package/src/proxy/feeds/epochs/epoch.test.ts +249 -0
- package/src/proxy/feeds/epochs/epoch.ts +181 -0
- package/src/proxy/feeds/epochs/finder.ts +282 -0
- package/src/proxy/feeds/epochs/index.ts +73 -0
- package/src/proxy/feeds/epochs/integration.test.ts +1336 -0
- package/src/proxy/feeds/epochs/test-utils.ts +274 -0
- package/src/proxy/feeds/epochs/types.ts +128 -0
- package/src/proxy/feeds/epochs/updater.ts +192 -0
- package/src/proxy/feeds/epochs/utils.ts +62 -0
- package/src/proxy/feeds/index.ts +5 -0
- package/src/proxy/feeds/sequence/async-finder.ts +31 -0
- package/src/proxy/feeds/sequence/finder.ts +73 -0
- package/src/proxy/feeds/sequence/index.ts +54 -0
- package/src/proxy/feeds/sequence/integration.test.ts +966 -0
- package/src/proxy/feeds/sequence/types.ts +103 -0
- package/src/proxy/feeds/sequence/updater.ts +71 -0
- package/src/proxy/index.ts +5 -0
- package/src/proxy/manifest-builder.test.ts +427 -0
- package/src/proxy/manifest-builder.ts +679 -0
- package/src/proxy/mantaray-encrypted.ts +78 -0
- package/src/proxy/mantaray.ts +104 -0
- package/src/proxy/types.ts +32 -0
- package/src/proxy/upload-data.ts +189 -0
- package/src/proxy/upload-encrypted-data.ts +658 -0
- package/src/schemas.ts +299 -0
- package/src/storage/debounced-uploader.ts +192 -0
- package/src/storage/utilization-store.ts +397 -0
- package/src/swarm-id-client.test.ts +99 -0
- package/src/swarm-id-client.ts +3095 -0
- package/src/swarm-id-proxy.ts +3891 -0
- package/src/sync/index.ts +28 -0
- package/src/sync/restore-account.ts +90 -0
- package/src/sync/serialization.ts +39 -0
- package/src/sync/store-interfaces.ts +62 -0
- package/src/sync/sync-account.test.ts +302 -0
- package/src/sync/sync-account.ts +396 -0
- package/src/sync/types.ts +11 -0
- package/src/test-fixtures.ts +109 -0
- package/src/types.ts +1651 -0
- package/src/utils/account-state-snapshot.test.ts +595 -0
- package/src/utils/account-state-snapshot.ts +94 -0
- package/src/utils/backup-encryption.test.ts +442 -0
- package/src/utils/backup-encryption.ts +352 -0
- package/src/utils/batch-utilization.ts +1309 -0
- package/src/utils/constants.ts +20 -0
- package/src/utils/hex.ts +27 -0
- package/src/utils/key-derivation.ts +197 -0
- package/src/utils/storage-managers.ts +365 -0
- package/src/utils/ttl.ts +129 -0
- package/src/utils/url.test.ts +136 -0
- package/src/utils/url.ts +71 -0
- package/src/utils/versioned-storage.ts +323 -0
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
import { Reference, MantarayNode, NULL_ADDRESS } from "@ethersphere/bee-js"
|
|
2
|
+
import { makeContentAddressedChunk } from "../chunk"
|
|
3
|
+
import { hexToUint8Array } from "../utils/hex"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mantaray v0.2 version hash (31 bytes).
|
|
7
|
+
* From bee-js: the first 31 bytes of keccak256("mantaray:0.2")
|
|
8
|
+
*/
|
|
9
|
+
const MANTARAY_VERSION_HASH = new Uint8Array([
|
|
10
|
+
0x57, 0x68, 0xb3, 0xb6, 0xa7, 0xdb, 0x56, 0xd2, 0x1d, 0x1a, 0xbf, 0xf4, 0x0d,
|
|
11
|
+
0x41, 0xce, 0xbf, 0xc8, 0x34, 0x48, 0xfe, 0xd8, 0xd7, 0xe9, 0xb0, 0x6e, 0xc0,
|
|
12
|
+
0xd3, 0xb0, 0x73, 0xf2, 0x8f,
|
|
13
|
+
])
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Fork flags for Mantaray format.
|
|
17
|
+
*/
|
|
18
|
+
const FORK_FLAG_VALUE_TYPE = 0x02 // TYPE_VALUE: has targetAddress (matches bee-js)
|
|
19
|
+
const FORK_FLAG_HAS_METADATA = 0x10 // TYPE_WITH_METADATA: has metadata bytes (matches bee-js)
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Result of building a /bzz/-compatible manifest.
|
|
23
|
+
*
|
|
24
|
+
* This is a FLAT manifest with no child nodes - just the root manifest.
|
|
25
|
+
* Bee's /bzz/ endpoint can directly resolve paths from this structure.
|
|
26
|
+
*
|
|
27
|
+
* IMPORTANT: The `data` field contains RAW manifest bytes (without span prefix).
|
|
28
|
+
* When uploaded via `uploadData()`, span will be added and the resulting address
|
|
29
|
+
* will match the pre-computed `address` field.
|
|
30
|
+
*/
|
|
31
|
+
export interface BzzCompatibleManifestResult {
|
|
32
|
+
/** The manifest chunk to upload. Raw bytes, no span. */
|
|
33
|
+
manifestChunk: {
|
|
34
|
+
data: Uint8Array
|
|
35
|
+
address: string
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build a /bzz/-compatible flat manifest with VALUE forks.
|
|
41
|
+
*
|
|
42
|
+
* Creates a flat manifest with two VALUE forks at root level:
|
|
43
|
+
* - "/" → VALUE fork with website-index-document metadata pointing to NULL
|
|
44
|
+
* - "index.bin" → VALUE fork pointing directly to content reference
|
|
45
|
+
*
|
|
46
|
+
* This is built manually because bee-js MantarayNode always creates EDGE forks
|
|
47
|
+
* with child nodes, which causes Bee to fail to resolve the content.
|
|
48
|
+
*
|
|
49
|
+
* When Bee accesses /bzz/manifest/:
|
|
50
|
+
* 1. Finds "/" fork, reads website-index-document metadata
|
|
51
|
+
* 2. Looks up "index.bin" fork at root level
|
|
52
|
+
* 3. "index.bin" is VALUE type → serves content directly
|
|
53
|
+
*
|
|
54
|
+
* Binary format (Mantaray v0.2):
|
|
55
|
+
* - obfuscationKey (32 bytes): All zeros for no obfuscation
|
|
56
|
+
* - versionHash (31 bytes): MANTARAY_VERSION_HASH
|
|
57
|
+
* - refBytesCount (1 byte): 32
|
|
58
|
+
* - entry (32 bytes): All zeros (root has no entry)
|
|
59
|
+
* - indexBitmap (32 bytes): Bits set for '/' (47) and 'i' (105)
|
|
60
|
+
* - fork1 (64+ bytes): "/" fork with metadata
|
|
61
|
+
* - fork2 (64+ bytes): "index.bin" fork with metadata
|
|
62
|
+
*
|
|
63
|
+
* @param contentReference - Reference to the actual content (32 bytes / 64 hex chars)
|
|
64
|
+
* @returns Single manifest chunk to upload
|
|
65
|
+
*/
|
|
66
|
+
export async function buildBzzCompatibleManifest(
|
|
67
|
+
contentReference: string | Uint8Array,
|
|
68
|
+
): Promise<BzzCompatibleManifestResult> {
|
|
69
|
+
const refBytes =
|
|
70
|
+
contentReference instanceof Uint8Array
|
|
71
|
+
? contentReference
|
|
72
|
+
: new Reference(contentReference).toUint8Array()
|
|
73
|
+
|
|
74
|
+
if (refBytes.length !== 32) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Invalid content reference length: ${refBytes.length} (expected 32 bytes)`,
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Prepare metadata JSON for both forks
|
|
81
|
+
const slashMetadata =
|
|
82
|
+
JSON.stringify({ "website-index-document": "index.bin" }) + "\n"
|
|
83
|
+
const indexMetadata =
|
|
84
|
+
JSON.stringify({
|
|
85
|
+
"Content-Type": "application/octet-stream",
|
|
86
|
+
Filename: "index.bin",
|
|
87
|
+
}) + "\n"
|
|
88
|
+
|
|
89
|
+
const slashMetadataBytes = new TextEncoder().encode(slashMetadata)
|
|
90
|
+
const indexMetadataBytes = new TextEncoder().encode(indexMetadata)
|
|
91
|
+
|
|
92
|
+
// Calculate sizes
|
|
93
|
+
// Header: 32 + 31 + 1 + 32 + 32 = 128 bytes
|
|
94
|
+
// Fork "/" : flags(1) + prefixLen(1) + prefix(30) + reference(32) + metaLen(2) + meta
|
|
95
|
+
// Fork "index.bin": flags(1) + prefixLen(1) + prefix(30) + reference(32) + metaLen(2) + meta
|
|
96
|
+
const HEADER_SIZE = 128
|
|
97
|
+
const FORK_BASE_SIZE = 66 // 1 + 1 + 30 + 32 + 2
|
|
98
|
+
const TOTAL_SIZE =
|
|
99
|
+
HEADER_SIZE +
|
|
100
|
+
FORK_BASE_SIZE +
|
|
101
|
+
slashMetadataBytes.length +
|
|
102
|
+
FORK_BASE_SIZE +
|
|
103
|
+
indexMetadataBytes.length
|
|
104
|
+
|
|
105
|
+
const manifest = new Uint8Array(TOTAL_SIZE)
|
|
106
|
+
let offset = 0
|
|
107
|
+
|
|
108
|
+
// 1. Obfuscation key (32 bytes) - all zeros for no obfuscation
|
|
109
|
+
offset += 32
|
|
110
|
+
|
|
111
|
+
// 2. Version hash (31 bytes)
|
|
112
|
+
manifest.set(MANTARAY_VERSION_HASH, offset)
|
|
113
|
+
offset += 31
|
|
114
|
+
|
|
115
|
+
// 3. refBytesCount (1 byte) - 32 for standard reference size
|
|
116
|
+
manifest[offset] = 32
|
|
117
|
+
offset += 1
|
|
118
|
+
|
|
119
|
+
// 4. Entry (32 bytes) - all zeros for root node
|
|
120
|
+
offset += 32
|
|
121
|
+
|
|
122
|
+
// 5. Index bitmap (32 bytes)
|
|
123
|
+
// Bit 47 set for '/' and bit 105 set for 'i' (start of "index.bin")
|
|
124
|
+
// Byte 47/8 = 5, bit 47%8 = 7 → byte 5 = 0x80
|
|
125
|
+
// Byte 105/8 = 13, bit 105%8 = 1 → byte 13 = 0x02
|
|
126
|
+
manifest[offset + 5] = 0x80 // '/' at position 47
|
|
127
|
+
manifest[offset + 13] = 0x02 // 'i' at position 105
|
|
128
|
+
offset += 32
|
|
129
|
+
|
|
130
|
+
// Forks are ordered by their first byte value
|
|
131
|
+
// '/' = 47 comes before 'i' = 105
|
|
132
|
+
|
|
133
|
+
// 6. Fork entry for '/' (first because 47 < 105)
|
|
134
|
+
// Flags: TYPE_WITH_METADATA only (0x10) - NO VALUE since reference is NULL
|
|
135
|
+
// The "/" fork is just a metadata carrier for website-index-document
|
|
136
|
+
manifest[offset] = FORK_FLAG_HAS_METADATA
|
|
137
|
+
offset += 1
|
|
138
|
+
|
|
139
|
+
// Prefix length
|
|
140
|
+
manifest[offset] = 1
|
|
141
|
+
offset += 1
|
|
142
|
+
|
|
143
|
+
// Prefix (30 bytes padded)
|
|
144
|
+
manifest[offset] = 0x2f // '/'
|
|
145
|
+
offset += 30
|
|
146
|
+
|
|
147
|
+
// Reference (32 bytes) - NULL_ADDRESS (all zeros) because "/" just has metadata
|
|
148
|
+
// Already zeros, just advance
|
|
149
|
+
offset += 32
|
|
150
|
+
|
|
151
|
+
// Metadata length (2 bytes big-endian)
|
|
152
|
+
manifest[offset] = (slashMetadataBytes.length >> 8) & 0xff
|
|
153
|
+
manifest[offset + 1] = slashMetadataBytes.length & 0xff
|
|
154
|
+
offset += 2
|
|
155
|
+
|
|
156
|
+
// Metadata content
|
|
157
|
+
manifest.set(slashMetadataBytes, offset)
|
|
158
|
+
offset += slashMetadataBytes.length
|
|
159
|
+
|
|
160
|
+
// 7. Fork entry for 'index.bin'
|
|
161
|
+
// Flags: TYPE_VALUE (0x02) + TYPE_WITH_METADATA (0x10) = 0x12
|
|
162
|
+
manifest[offset] = FORK_FLAG_VALUE_TYPE | FORK_FLAG_HAS_METADATA
|
|
163
|
+
offset += 1
|
|
164
|
+
|
|
165
|
+
// Prefix length - "index.bin" = 9 characters
|
|
166
|
+
manifest[offset] = 9
|
|
167
|
+
offset += 1
|
|
168
|
+
|
|
169
|
+
// Prefix (30 bytes padded)
|
|
170
|
+
const indexBinBytes = new TextEncoder().encode("index.bin")
|
|
171
|
+
manifest.set(indexBinBytes, offset)
|
|
172
|
+
offset += 30
|
|
173
|
+
|
|
174
|
+
// Reference (32 bytes) - the actual content reference
|
|
175
|
+
manifest.set(refBytes, offset)
|
|
176
|
+
offset += 32
|
|
177
|
+
|
|
178
|
+
// Metadata length (2 bytes big-endian)
|
|
179
|
+
manifest[offset] = (indexMetadataBytes.length >> 8) & 0xff
|
|
180
|
+
manifest[offset + 1] = indexMetadataBytes.length & 0xff
|
|
181
|
+
offset += 2
|
|
182
|
+
|
|
183
|
+
// Metadata content
|
|
184
|
+
manifest.set(indexMetadataBytes, offset)
|
|
185
|
+
offset += indexMetadataBytes.length
|
|
186
|
+
|
|
187
|
+
// Calculate manifest address
|
|
188
|
+
const rootChunk = makeContentAddressedChunk(manifest)
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
manifestChunk: {
|
|
192
|
+
data: manifest,
|
|
193
|
+
address: rootChunk.address.toHex(),
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Result of building a /bzz/-compatible MantarayNode manifest.
|
|
200
|
+
*/
|
|
201
|
+
export interface BzzManifestNodeResult {
|
|
202
|
+
/** The MantarayNode to be uploaded with saveMantarayTreeRecursively */
|
|
203
|
+
manifestNode: MantarayNode
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Build a /bzz/-compatible manifest as a MantarayNode.
|
|
208
|
+
*
|
|
209
|
+
* This function creates a MantarayNode with the proper structure for /bzz/ access:
|
|
210
|
+
* - "/" fork with website-index-document metadata pointing to "index.bin"
|
|
211
|
+
* - "index.bin" fork pointing to the actual content reference
|
|
212
|
+
*
|
|
213
|
+
* IMPORTANT: After calling this function, use saveMantarayTreeRecursively() to
|
|
214
|
+
* upload the manifest. This ensures all child nodes are uploaded bottom-up and
|
|
215
|
+
* their addresses are correctly set from Bee's responses.
|
|
216
|
+
*
|
|
217
|
+
* Usage:
|
|
218
|
+
* ```typescript
|
|
219
|
+
* const { manifestNode } = buildBzzManifestNode(contentReference)
|
|
220
|
+
* const result = await saveMantarayTreeRecursively(manifestNode, async (data, isRoot) => {
|
|
221
|
+
* const uploadResult = await uploadData(bee, stamper, data, uploadOptions)
|
|
222
|
+
* return { reference: uploadResult.reference }
|
|
223
|
+
* })
|
|
224
|
+
* // result.rootReference is the /bzz/ compatible manifest address
|
|
225
|
+
* ```
|
|
226
|
+
*
|
|
227
|
+
* @param contentReference - Reference to the actual content (32 bytes / 64 hex chars)
|
|
228
|
+
* @returns MantarayNode ready for upload with saveMantarayTreeRecursively
|
|
229
|
+
*/
|
|
230
|
+
export function buildBzzManifestNode(
|
|
231
|
+
contentReference: string | Uint8Array,
|
|
232
|
+
): BzzManifestNodeResult {
|
|
233
|
+
const refBytes =
|
|
234
|
+
contentReference instanceof Uint8Array
|
|
235
|
+
? contentReference
|
|
236
|
+
: hexToUint8Array(contentReference)
|
|
237
|
+
|
|
238
|
+
if (refBytes.length !== 32) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
`Invalid content reference length: ${refBytes.length} (expected 32 bytes)`,
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const manifest = new MantarayNode()
|
|
245
|
+
|
|
246
|
+
// Add "/" fork with website-index-document metadata
|
|
247
|
+
// This tells Bee to look for "index.bin" when accessing the root path
|
|
248
|
+
manifest.addFork("/", NULL_ADDRESS, {
|
|
249
|
+
"website-index-document": "index.bin",
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
// Add "index.bin" fork pointing to the actual content
|
|
253
|
+
manifest.addFork("index.bin", refBytes, {
|
|
254
|
+
"Content-Type": "application/octet-stream",
|
|
255
|
+
Filename: "index.bin",
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
return { manifestNode: manifest }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* @deprecated Use buildBzzManifestNode() with saveMantarayTreeRecursively() instead.
|
|
263
|
+
* This function manually builds binary format but doesn't upload child nodes,
|
|
264
|
+
* which causes Bee to fail to resolve content via /bzz/.
|
|
265
|
+
*/
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @deprecated Use buildBzzCompatibleManifest() for /bzz/ compatibility.
|
|
269
|
+
* This function creates a manifest that won't work with Bee's /bzz/ endpoint
|
|
270
|
+
* because it embeds the content reference directly in the manifest instead
|
|
271
|
+
* of using the proper two-level structure (root → child → content).
|
|
272
|
+
*
|
|
273
|
+
* Build a minimal mantaray manifest for /bzz/ feed compatibility.
|
|
274
|
+
*
|
|
275
|
+
* This creates a manifest with a single "/" fork pointing to a content reference.
|
|
276
|
+
* The manifest is built manually (not using bee-js MantarayNode.marshal()) because
|
|
277
|
+
* bee-js doesn't embed the targetAddress correctly for value-type nodes.
|
|
278
|
+
*
|
|
279
|
+
* Binary format (Mantaray v0.2):
|
|
280
|
+
* - obfuscationKey (32 bytes): All zeros for no obfuscation
|
|
281
|
+
* - versionHash (31 bytes): MANTARAY_VERSION_HASH
|
|
282
|
+
* - refBytesCount (1 byte): 32 (size of entry reference)
|
|
283
|
+
* - entry (32 bytes): All zeros (root has no entry)
|
|
284
|
+
* - indexBitmap (32 bytes): Bitmap with bit 47 set (for '/')
|
|
285
|
+
* - fork (64+ bytes): flags + prefixLen + prefix(30) + reference(32) + metadata
|
|
286
|
+
*
|
|
287
|
+
* @param contentReference - Reference to the actual content (32 bytes / 64 hex chars)
|
|
288
|
+
* @returns Marshaled manifest bytes
|
|
289
|
+
*/
|
|
290
|
+
export function buildMinimalManifest(
|
|
291
|
+
contentReference: string | Uint8Array,
|
|
292
|
+
): Uint8Array {
|
|
293
|
+
// Normalize reference to Uint8Array
|
|
294
|
+
const refBytes =
|
|
295
|
+
contentReference instanceof Uint8Array
|
|
296
|
+
? contentReference
|
|
297
|
+
: new Reference(contentReference).toUint8Array()
|
|
298
|
+
|
|
299
|
+
if (refBytes.length !== 32) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Invalid content reference length: ${refBytes.length} (expected 32 bytes)`,
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Calculate total size
|
|
306
|
+
// Header: 32 + 31 + 1 + 32 + 32 = 128 bytes
|
|
307
|
+
// Fork: 1 + 1 + 30 + 32 = 64 bytes (no metadata for simplicity)
|
|
308
|
+
// Total: 192 bytes
|
|
309
|
+
const HEADER_SIZE = 128
|
|
310
|
+
const FORK_SIZE = 64
|
|
311
|
+
const TOTAL_SIZE = HEADER_SIZE + FORK_SIZE
|
|
312
|
+
|
|
313
|
+
const manifest = new Uint8Array(TOTAL_SIZE)
|
|
314
|
+
let offset = 0
|
|
315
|
+
|
|
316
|
+
// 1. Obfuscation key (32 bytes) - all zeros for no obfuscation
|
|
317
|
+
offset += 32
|
|
318
|
+
|
|
319
|
+
// 2. Version hash (31 bytes)
|
|
320
|
+
manifest.set(MANTARAY_VERSION_HASH, offset)
|
|
321
|
+
offset += 31
|
|
322
|
+
|
|
323
|
+
// 3. refBytesCount (1 byte) - 32 for standard reference size
|
|
324
|
+
manifest[offset] = 32
|
|
325
|
+
offset += 1
|
|
326
|
+
|
|
327
|
+
// 4. Entry (32 bytes) - all zeros for root node
|
|
328
|
+
offset += 32
|
|
329
|
+
|
|
330
|
+
// 5. Index bitmap (32 bytes) - bit 47 set for '/'
|
|
331
|
+
// Byte index = 47 / 8 = 5, bit position = 47 % 8 = 7 (MSB)
|
|
332
|
+
// So we set byte 5 to 0x80 (bit 7 set)
|
|
333
|
+
manifest[offset + 5] = 0x80
|
|
334
|
+
offset += 32
|
|
335
|
+
|
|
336
|
+
// 6. Fork entry for '/'
|
|
337
|
+
// Flags: VALUE_TYPE (reference is the target)
|
|
338
|
+
manifest[offset] = FORK_FLAG_VALUE_TYPE
|
|
339
|
+
offset += 1
|
|
340
|
+
|
|
341
|
+
// Prefix length
|
|
342
|
+
manifest[offset] = 1
|
|
343
|
+
offset += 1
|
|
344
|
+
|
|
345
|
+
// Prefix (30 bytes padded)
|
|
346
|
+
manifest[offset] = 0x2f // '/'
|
|
347
|
+
offset += 30
|
|
348
|
+
|
|
349
|
+
// Reference (32 bytes) - the actual content reference
|
|
350
|
+
manifest.set(refBytes, offset)
|
|
351
|
+
offset += 32
|
|
352
|
+
|
|
353
|
+
return manifest
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Maximum CAC payload size for /bzz/ compatible feed uploads.
|
|
358
|
+
*
|
|
359
|
+
* For the /chunks endpoint to detect SOC correctly, total SOC size must be > 4104 bytes.
|
|
360
|
+
* SOC structure: identifier(32) + signature(65) + span(8) + payload(N)
|
|
361
|
+
* For N=4096: total = 32 + 65 + 8 + 4096 = 4201 bytes > 4104 ✓
|
|
362
|
+
*/
|
|
363
|
+
export const MAX_PADDED_PAYLOAD_SIZE = 4096
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Pad payload to 4096 bytes for SOC detection by /chunks endpoint.
|
|
367
|
+
*
|
|
368
|
+
* The span field in the CAC contains the actual payload size (before padding),
|
|
369
|
+
* so Bee's joiner will only read the actual data and ignore padding.
|
|
370
|
+
*
|
|
371
|
+
* @param payload - Original payload (must be <= 4096 bytes)
|
|
372
|
+
* @returns Padded payload (exactly 4096 bytes)
|
|
373
|
+
*/
|
|
374
|
+
export function padPayloadForSOCDetection(payload: Uint8Array): Uint8Array {
|
|
375
|
+
if (payload.length > MAX_PADDED_PAYLOAD_SIZE) {
|
|
376
|
+
throw new Error(
|
|
377
|
+
`Payload too large to pad: ${payload.length} > ${MAX_PADDED_PAYLOAD_SIZE}`,
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (payload.length === MAX_PADDED_PAYLOAD_SIZE) {
|
|
382
|
+
return payload // Already at max size
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Create padded buffer with zeros
|
|
386
|
+
const padded = new Uint8Array(MAX_PADDED_PAYLOAD_SIZE)
|
|
387
|
+
padded.set(payload, 0)
|
|
388
|
+
|
|
389
|
+
return padded
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Extract content reference from a minimal mantaray manifest.
|
|
394
|
+
*
|
|
395
|
+
* This is the reverse of buildMinimalManifest() - it parses the manifest
|
|
396
|
+
* binary format and extracts the "/" fork's targetAddress.
|
|
397
|
+
*
|
|
398
|
+
* Binary format (Mantaray v0.2):
|
|
399
|
+
* - Offset 0-31: obfuscationKey
|
|
400
|
+
* - Offset 32-62: versionHash (31 bytes)
|
|
401
|
+
* - Offset 63: refBytesCount
|
|
402
|
+
* - Offset 64-95: entry (refBytesCount bytes, assuming 32)
|
|
403
|
+
* - Offset 96-127: indexBitmap
|
|
404
|
+
* - Offset 128+: forks (flags + prefixLen + prefix + reference + metadata)
|
|
405
|
+
*
|
|
406
|
+
* For a minimal manifest with just "/" fork:
|
|
407
|
+
* - Fork flags at offset 128
|
|
408
|
+
* - Prefix length at offset 129
|
|
409
|
+
* - Prefix (30 bytes) at offset 130
|
|
410
|
+
* - Reference (32 bytes) at offset 160
|
|
411
|
+
*
|
|
412
|
+
* @param manifestData - Raw manifest bytes (from feed payload)
|
|
413
|
+
* @returns Content reference as 64 hex characters
|
|
414
|
+
*/
|
|
415
|
+
export function extractReferenceFromManifest(manifestData: Uint8Array): string {
|
|
416
|
+
// Minimum size check: header (128) + minimal fork (64)
|
|
417
|
+
if (manifestData.length < 192) {
|
|
418
|
+
throw new Error(
|
|
419
|
+
`Manifest too small: ${manifestData.length} bytes (expected at least 192)`,
|
|
420
|
+
)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Header offsets (for reference)
|
|
424
|
+
// Offset 0-31: obfuscationKey
|
|
425
|
+
// Offset 32-62: versionHash (31 bytes)
|
|
426
|
+
// Offset 63: refBytesCount
|
|
427
|
+
// Offset 64-95: entry (refBytesCount bytes, assuming 32)
|
|
428
|
+
// Offset 96-127: indexBitmap
|
|
429
|
+
// Offset 128+: forks
|
|
430
|
+
const REF_BYTES_COUNT_OFFSET = 63
|
|
431
|
+
const INDEX_BITMAP_OFFSET = 96
|
|
432
|
+
const FORKS_OFFSET = 128
|
|
433
|
+
|
|
434
|
+
// Read refBytesCount to determine entry size
|
|
435
|
+
const refBytesCount = manifestData[REF_BYTES_COUNT_OFFSET]
|
|
436
|
+
if (refBytesCount !== 32) {
|
|
437
|
+
console.warn(
|
|
438
|
+
`[ManifestBuilder] Unexpected refBytesCount: ${refBytesCount} (expected 32)`,
|
|
439
|
+
)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Verify "/" fork exists in index bitmap
|
|
443
|
+
// Byte 5, bit 7 should be set for '/' (byte value 47)
|
|
444
|
+
// Bit ordering: value N is at byte N/8, bit N%8
|
|
445
|
+
// So value 47 is at byte 5, bit 7 (mask 0x80)
|
|
446
|
+
const indexBitmap = manifestData.slice(
|
|
447
|
+
INDEX_BITMAP_OFFSET,
|
|
448
|
+
INDEX_BITMAP_OFFSET + 32,
|
|
449
|
+
)
|
|
450
|
+
const slashByteIndex = Math.floor(47 / 8) // = 5
|
|
451
|
+
const slashBitMask = 1 << (47 % 8) // = 0x80
|
|
452
|
+
|
|
453
|
+
if ((indexBitmap[slashByteIndex] & slashBitMask) === 0) {
|
|
454
|
+
throw new Error('Manifest does not contain "/" fork')
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Count forks before "/" to find its offset
|
|
458
|
+
// We need to count all set bits for byte values less than 47
|
|
459
|
+
let forkIndex = 0
|
|
460
|
+
// Count all bits in bytes 0-4
|
|
461
|
+
for (let byte = 0; byte < slashByteIndex; byte++) {
|
|
462
|
+
forkIndex += popcount8(indexBitmap[byte])
|
|
463
|
+
}
|
|
464
|
+
// Count bits in byte 5 for values 40-46 (bits 0-6)
|
|
465
|
+
for (let bit = 0; bit < 47 % 8; bit++) {
|
|
466
|
+
if (indexBitmap[slashByteIndex] & (1 << bit)) {
|
|
467
|
+
forkIndex++
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Calculate fork offset
|
|
472
|
+
// Each fork is: flags(1) + prefixLen(1) + prefix(30) + reference(32) + metadata(variable)
|
|
473
|
+
// For simplicity, assume all forks before "/" have the same minimal structure
|
|
474
|
+
// In a minimal manifest, "/" should be the first (and only) fork
|
|
475
|
+
|
|
476
|
+
let forkOffset = FORKS_OFFSET
|
|
477
|
+
|
|
478
|
+
// Skip previous forks if any
|
|
479
|
+
for (let i = 0; i < forkIndex; i++) {
|
|
480
|
+
// Read flags to check if metadata exists
|
|
481
|
+
const flags = manifestData[forkOffset]
|
|
482
|
+
const hasMetadata = (flags & FORK_FLAG_HAS_METADATA) !== 0
|
|
483
|
+
|
|
484
|
+
forkOffset += 1 + 1 + 30 + 32 // flags + prefixLen + prefix + reference
|
|
485
|
+
|
|
486
|
+
if (hasMetadata) {
|
|
487
|
+
// Read metadata length (2 bytes big-endian)
|
|
488
|
+
const metadataLen =
|
|
489
|
+
(manifestData[forkOffset] << 8) | manifestData[forkOffset + 1]
|
|
490
|
+
forkOffset += 2 + metadataLen
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Now at the "/" fork
|
|
495
|
+
const prefixLen = manifestData[forkOffset + 1]
|
|
496
|
+
|
|
497
|
+
// Verify it's actually "/"
|
|
498
|
+
if (prefixLen !== 1 || manifestData[forkOffset + 2] !== 0x2f) {
|
|
499
|
+
throw new Error(
|
|
500
|
+
`Expected "/" prefix, got length ${prefixLen} and byte 0x${manifestData[forkOffset + 2].toString(16)}`,
|
|
501
|
+
)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Reference is at offset: forkOffset + 1 (flags) + 1 (prefixLen) + 30 (prefix) = forkOffset + 32
|
|
505
|
+
const referenceOffset = forkOffset + 32
|
|
506
|
+
const reference = manifestData.slice(referenceOffset, referenceOffset + 32)
|
|
507
|
+
|
|
508
|
+
if (isAllZeros(reference)) {
|
|
509
|
+
throw new Error('Manifest "/" fork has zero reference')
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const hexRef = new Reference(reference).toHex()
|
|
513
|
+
|
|
514
|
+
return hexRef
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Count number of set bits in a byte (population count).
|
|
519
|
+
*/
|
|
520
|
+
function popcount8(n: number): number {
|
|
521
|
+
let count = 0
|
|
522
|
+
while (n) {
|
|
523
|
+
count += n & 1
|
|
524
|
+
n >>>= 1
|
|
525
|
+
}
|
|
526
|
+
return count
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Check if a Uint8Array is all zeros.
|
|
531
|
+
*/
|
|
532
|
+
function isAllZeros(arr: Uint8Array): boolean {
|
|
533
|
+
for (let i = 0; i < arr.length; i++) {
|
|
534
|
+
if (arr[i] !== 0) return false
|
|
535
|
+
}
|
|
536
|
+
return true
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Extract entry (targetAddress) from a leaf manifest node.
|
|
541
|
+
*
|
|
542
|
+
* In Mantaray v0.2, leaf nodes store their targetAddress in the entry field
|
|
543
|
+
* (bytes 64-95), not in a fork. This is used for the second level of parsing
|
|
544
|
+
* when using buildBzzCompatibleManifest().
|
|
545
|
+
*
|
|
546
|
+
* Binary format (Mantaray v0.2):
|
|
547
|
+
* - Offset 0-31: obfuscationKey
|
|
548
|
+
* - Offset 32-62: versionHash (31 bytes)
|
|
549
|
+
* - Offset 63: refBytesCount
|
|
550
|
+
* - Offset 64-95: entry (32 bytes) ← targetAddress for leaf nodes
|
|
551
|
+
* - Offset 96-127: indexBitmap
|
|
552
|
+
* - Offset 128+: forks
|
|
553
|
+
*
|
|
554
|
+
* @param manifestData - Raw manifest bytes (child chunk data from two-level structure)
|
|
555
|
+
* @returns Content reference as 64 hex characters
|
|
556
|
+
*/
|
|
557
|
+
export function extractEntryFromManifest(manifestData: Uint8Array): string {
|
|
558
|
+
if (manifestData.length < 96) {
|
|
559
|
+
throw new Error(
|
|
560
|
+
`Manifest too small: ${manifestData.length} bytes (expected at least 96)`,
|
|
561
|
+
)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const ENTRY_OFFSET = 64
|
|
565
|
+
const entry = manifestData.slice(ENTRY_OFFSET, ENTRY_OFFSET + 32)
|
|
566
|
+
|
|
567
|
+
if (isAllZeros(entry)) {
|
|
568
|
+
throw new Error("Manifest entry is zero")
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const hexRef = new Reference(entry).toHex()
|
|
572
|
+
|
|
573
|
+
return hexRef
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Extract content reference from a flat /bzz/-compatible manifest.
|
|
578
|
+
*
|
|
579
|
+
* In the flat structure, the manifest has:
|
|
580
|
+
* - "/" fork with NULL_ADDRESS and website-index-document metadata
|
|
581
|
+
* - "index.bin" fork with VALUE type pointing to content reference
|
|
582
|
+
*
|
|
583
|
+
* This function looks for the "index.bin" fork ('i' = ASCII 105) and
|
|
584
|
+
* extracts its reference.
|
|
585
|
+
*
|
|
586
|
+
* @param manifestData - Raw manifest bytes (flat manifest)
|
|
587
|
+
* @returns Content reference as 64 hex characters
|
|
588
|
+
*/
|
|
589
|
+
export function extractContentFromFlatManifest(
|
|
590
|
+
manifestData: Uint8Array,
|
|
591
|
+
): string {
|
|
592
|
+
if (manifestData.length < 192) {
|
|
593
|
+
throw new Error(
|
|
594
|
+
`Manifest too small: ${manifestData.length} bytes (expected at least 192)`,
|
|
595
|
+
)
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const INDEX_BITMAP_OFFSET = 96
|
|
599
|
+
const FORKS_OFFSET = 128
|
|
600
|
+
|
|
601
|
+
// Look for 'i' (105) in the index bitmap
|
|
602
|
+
// Byte index = 105 / 8 = 13, bit position = 105 % 8 = 1
|
|
603
|
+
const indexBitmap = manifestData.slice(
|
|
604
|
+
INDEX_BITMAP_OFFSET,
|
|
605
|
+
INDEX_BITMAP_OFFSET + 32,
|
|
606
|
+
)
|
|
607
|
+
const iByteIndex = Math.floor(105 / 8) // = 13
|
|
608
|
+
const iBitMask = 1 << (105 % 8) // = 0x02
|
|
609
|
+
|
|
610
|
+
if ((indexBitmap[iByteIndex] & iBitMask) === 0) {
|
|
611
|
+
throw new Error('Manifest does not contain "index.bin" fork')
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Count forks before 'i' to find its offset
|
|
615
|
+
let forkIndex = 0
|
|
616
|
+
for (let byte = 0; byte < iByteIndex; byte++) {
|
|
617
|
+
forkIndex += popcount8(indexBitmap[byte])
|
|
618
|
+
}
|
|
619
|
+
// Count bits in byte 13 for values less than 105
|
|
620
|
+
for (let bit = 0; bit < 105 % 8; bit++) {
|
|
621
|
+
if (indexBitmap[iByteIndex] & (1 << bit)) {
|
|
622
|
+
forkIndex++
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Navigate to the correct fork by skipping previous forks
|
|
627
|
+
let forkOffset = FORKS_OFFSET
|
|
628
|
+
for (let i = 0; i < forkIndex; i++) {
|
|
629
|
+
const flags = manifestData[forkOffset]
|
|
630
|
+
const hasMetadata = (flags & FORK_FLAG_HAS_METADATA) !== 0
|
|
631
|
+
forkOffset += 1 + 1 + 30 + 32 // flags + prefixLen + prefix + reference
|
|
632
|
+
|
|
633
|
+
if (hasMetadata) {
|
|
634
|
+
const metadataLen =
|
|
635
|
+
(manifestData[forkOffset] << 8) | manifestData[forkOffset + 1]
|
|
636
|
+
forkOffset += 2 + metadataLen
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Now at the "index.bin" fork
|
|
641
|
+
const forkFlags = manifestData[forkOffset]
|
|
642
|
+
const prefixLen = manifestData[forkOffset + 1]
|
|
643
|
+
|
|
644
|
+
// Verify it starts with 'i' (index.bin)
|
|
645
|
+
if (manifestData[forkOffset + 2] !== 0x69) {
|
|
646
|
+
// 'i'
|
|
647
|
+
throw new Error(
|
|
648
|
+
`Expected "index.bin" prefix, got byte 0x${manifestData[forkOffset + 2].toString(16)}`,
|
|
649
|
+
)
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Optionally verify full prefix "index.bin" (9 bytes)
|
|
653
|
+
if (prefixLen >= 9) {
|
|
654
|
+
const prefixBytes = manifestData.slice(forkOffset + 2, forkOffset + 2 + 9)
|
|
655
|
+
const expectedPrefix = new TextEncoder().encode("index.bin")
|
|
656
|
+
const prefixMatches = prefixBytes.every((b, i) => b === expectedPrefix[i])
|
|
657
|
+
if (!prefixMatches) {
|
|
658
|
+
console.warn('[ManifestBuilder] Prefix does not fully match "index.bin"')
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Check if this is a value type (reference is the target)
|
|
663
|
+
const isValueType = (forkFlags & FORK_FLAG_VALUE_TYPE) !== 0
|
|
664
|
+
if (!isValueType) {
|
|
665
|
+
throw new Error('"index.bin" fork is not a VALUE type')
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Reference is at offset: forkOffset + 1 (flags) + 1 (prefixLen) + 30 (prefix) = forkOffset + 32
|
|
669
|
+
const referenceOffset = forkOffset + 32
|
|
670
|
+
const reference = manifestData.slice(referenceOffset, referenceOffset + 32)
|
|
671
|
+
|
|
672
|
+
if (isAllZeros(reference)) {
|
|
673
|
+
throw new Error('Manifest "index.bin" fork has zero reference')
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const hexRef = new Reference(reference).toHex()
|
|
677
|
+
|
|
678
|
+
return hexRef
|
|
679
|
+
}
|