-
Notifications
You must be signed in to change notification settings - Fork 650
Expand file tree
/
Copy pathshardParser.ts
More file actions
149 lines (125 loc) · 5.34 KB
/
shardParser.ts
File metadata and controls
149 lines (125 loc) · 5.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import { SHARD_FOOTER_VERSION, SHARD_HEADER_VERSION, SHARD_MAGIC_TAG } from "./uploadShards";
const HASH_LENGTH = 32;
const XORB_HASH_BOOKEND = "ff".repeat(HASH_LENGTH);
// Read 4 uint64 in little endian and convert to hex
function readHashFromArray(array: Uint8Array, offset: number): string {
let hash = "";
for (let i = 0; i < HASH_LENGTH; i += 8) {
hash += `${array[offset + i + 7].toString(16).padStart(2, "0")}${array[offset + i + 6]
.toString(16)
.padStart(2, "0")}${array[offset + i + 5].toString(16).padStart(2, "0")}${array[offset + i + 4]
.toString(16)
.padStart(2, "0")}${array[offset + i + 3].toString(16).padStart(2, "0")}${array[offset + i + 2]
.toString(16)
.padStart(2, "0")}${array[offset + i + 1].toString(16).padStart(2, "0")}${array[offset + i]
.toString(16)
.padStart(2, "0")}`;
}
return hash;
}
export interface ShardData {
hmacKey: string;
xorbs: Array<{
hash: string;
chunks: Array<{
hash: string;
startOffset: number;
unpackedLength: number;
}>;
}>;
}
export async function parseShardData(shardBlob: Blob): Promise<ShardData> {
const shard = new Uint8Array(await shardBlob.arrayBuffer());
const shardView = new DataView(shard.buffer);
const magicTag = shard.slice(0, SHARD_MAGIC_TAG.length);
if (!magicTag.every((byte, i) => byte === SHARD_MAGIC_TAG[i])) {
throw new Error("Invalid shard magic tag");
}
const version = shardView.getBigUint64(SHARD_MAGIC_TAG.length, true);
if (version !== SHARD_HEADER_VERSION) {
throw new Error(`Invalid shard version: ${version}`);
}
const footerSize = Number(shardView.getBigUint64(SHARD_MAGIC_TAG.length + 8, true));
// Read footer to get section offsets
const footerStart = shard.length - footerSize;
const footerVersion = shardView.getBigUint64(footerStart, true);
if (footerVersion !== SHARD_FOOTER_VERSION) {
throw new Error(`Invalid shard footer version: ${footerVersion}`);
}
// version: u64, // Footer version (must be 1)
// file_info_offset: u64, // Offset to file info section
// xorb_info_offset: u64, // Offset to xorb info section
// file_lookup_offset: u64, // Offset to file lookup table
// file_lookup_num_entry: u64, // Number of file lookup entries
// xorb_lookup_offset: u64, // Offset to xorb lookup table
// xorb_lookup_num_entry: u64, // Number of xorb lookup entries
// chunk_lookup_offset: u64, // Offset to chunk lookup table
// chunk_lookup_num_entry: u64, // Number of chunk lookup entries
// chunk_hash_hmac_key: [u64; 4], // HMAC key for chunk hashes (32 bytes)
// shard_creation_timestamp: u64, // Creation time (seconds since epoch)
// shard_key_expiry: u64, // Expiry time (seconds since epoch)
// _buffer: [u64; 6], // Reserved space (48 bytes)
// stored_bytes_on_disk: u64, // Total bytes stored on disk
// materialized_bytes: u64, // Total materialized bytes
// stored_bytes: u64, // Total stored bytes
// footer_offset: u64,
// const fileInfoStart = Number(shardView.getBigUint64(footerStart + 8, true));
const xorbInfoStart = Number(shardView.getBigUint64(footerStart + 16, true));
const fileLookupStart = Number(shardView.getBigUint64(footerStart + 24, true));
// const numFileLookups = Number(shardView.getBigUint64(footerStart + 32, true));
// const xorbLookupStart = Number(shardView.getBigUint64(footerStart + 40, true));
// const numXorbLookups = Number(shardView.getBigUint64(footerStart + 48, true));
// const chunkLookupStart = Number(shardView.getBigUint64(footerStart + 56, true));
// const numChunkLookups = Number(shardView.getBigUint64(footerStart + 64, true));
const hmacKey = readHashFromArray(shard, footerStart + 72);
// const shardCreationTimestamp = Number(shardView.getBigUint64(footerStart + 104, true));
// const shardKeyExpiry = Number(shardView.getBigUint64(footerStart + 112, true));
// const storedBytesOnDisk = Number(shardView.getBigUint64(footerStart + 168, true));
// const materializedBytes = Number(shardView.getBigUint64(footerStart + 176, true));
// const storedBytes = Number(shardView.getBigUint64(footerStart + 184, true));
// const footerOffset = Number(shardView.getBigUint64(footerStart + 192, true));
// Parse XORB Info Section
const xorbs: ShardData["xorbs"] = [];
let offset = xorbInfoStart;
while (offset < fileLookupStart) {
// Read xorb entry
const xorbHash = readHashFromArray(shard, offset);
offset += HASH_LENGTH;
if (xorbHash === XORB_HASH_BOOKEND) {
break;
}
// const flags = shardView.getUint32(offset, true);
offset += 4;
const chunkCount = shardView.getUint32(offset, true);
offset += 4;
// const numBytesInXorb = shardView.getUint32(offset, true);
offset += 4;
// const numBytesUnpacked = shardView.getUint32(offset, true);
offset += 4;
// Read chunks for this xorb
const chunks: ShardData["xorbs"][number]["chunks"] = [];
for (let i = 0; i < chunkCount; i++) {
const chunkHash = readHashFromArray(shard, offset);
offset += HASH_LENGTH;
const startOffset = shardView.getUint32(offset, true);
offset += 4;
const length = shardView.getUint32(offset, true);
offset += 4;
// Skip reserved 8 bytes
offset += 8;
chunks.push({
hash: chunkHash,
startOffset,
unpackedLength: length,
});
}
xorbs.push({
hash: xorbHash,
chunks,
});
}
return {
hmacKey,
xorbs,
};
}