esp-nvs-utils 0.1.0

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.
@@ -0,0 +1,248 @@
1
+ //#region src/constants.ts
2
+ /** NVS page size in bytes. */
3
+ const PAGE_SIZE = 4096;
4
+ /** Page state: page is currently being written. */
5
+ const PAGE_STATE_ACTIVE = 4294967294;
6
+ /** Page state: page is full, no more entries can be written. */
7
+ const PAGE_STATE_FULL = 4294967292;
8
+ /** Map from NVS encoding string to type code. */
9
+ const ENCODING_TO_TYPE_CODE = {
10
+ u8: 1,
11
+ i8: 17,
12
+ u16: 2,
13
+ i16: 18,
14
+ u32: 4,
15
+ i32: 20,
16
+ u64: 8,
17
+ i64: 24,
18
+ string: 33,
19
+ blob_data: 66
20
+ };
21
+ //#endregion
22
+ //#region src/decoder.ts
23
+ function readKey(entry) {
24
+ let end = 8;
25
+ while (end < 24 && entry[end] !== 0) end++;
26
+ return new TextDecoder().decode(entry.subarray(8, end));
27
+ }
28
+ function entryBitmapState(bitmap, idx) {
29
+ const bitNum = idx * 2;
30
+ const byteIdx = bitNum >> 3;
31
+ const bitOffset = bitNum & 7;
32
+ return bitmap[byteIdx] >> bitOffset & 3;
33
+ }
34
+ function parsePrimitive(typeCode, inlineData) {
35
+ const view = new DataView(inlineData.buffer, inlineData.byteOffset, inlineData.byteLength);
36
+ switch (typeCode) {
37
+ case 1: return view.getUint8(0);
38
+ case 17: return view.getInt8(0);
39
+ case 2: return view.getUint16(0, true);
40
+ case 18: return view.getInt16(0, true);
41
+ case 4: return view.getUint32(0, true);
42
+ case 20: return view.getInt32(0, true);
43
+ case 8: return view.getBigUint64(0, true);
44
+ case 24: return view.getBigInt64(0, true);
45
+ default: throw new Error(`Unknown primitive type code: 0x${typeCode.toString(16)}`);
46
+ }
47
+ }
48
+ const PRIMITIVE_TYPES = new Set([
49
+ 1,
50
+ 17,
51
+ 2,
52
+ 18,
53
+ 4,
54
+ 20,
55
+ 8,
56
+ 24
57
+ ]);
58
+ const VARLEN_TYPES = new Set([
59
+ 33,
60
+ 65,
61
+ 66,
62
+ 72
63
+ ]);
64
+ /**
65
+ * Parse all valid written entries from a single 4096-byte page.
66
+ * Returns entries in slot order, including namespace entries.
67
+ */
68
+ function parsePage(pageData) {
69
+ const bitmap = pageData.subarray(32, 64);
70
+ const entries = [];
71
+ let i = 0;
72
+ while (i < 126) {
73
+ if (entryBitmapState(bitmap, i) !== 2) {
74
+ i++;
75
+ continue;
76
+ }
77
+ const entryOff = 64 + i * 32;
78
+ const entry = pageData.subarray(entryOff, entryOff + 32);
79
+ const nsIdx = entry[0];
80
+ const typeCode = entry[1];
81
+ let span = entry[2];
82
+ const chunkIndex = entry[3];
83
+ const safeSpan = span === 0 || span === 255 ? 1 : span;
84
+ const parsed = {
85
+ nsIdx,
86
+ typeCode,
87
+ span: safeSpan,
88
+ chunkIndex,
89
+ key: readKey(entry),
90
+ raw: entry,
91
+ inlineData: entry.subarray(24, 32),
92
+ entryIndex: i
93
+ };
94
+ if (VARLEN_TYPES.has(typeCode) && typeCode !== 72) {
95
+ const childCount = safeSpan - 1;
96
+ if (childCount > 0) {
97
+ const childStart = 64 + (i + 1) * 32;
98
+ const childEnd = childStart + childCount * 32;
99
+ parsed.childData = pageData.subarray(childStart, childEnd);
100
+ }
101
+ }
102
+ entries.push(parsed);
103
+ i += safeSpan;
104
+ }
105
+ return entries;
106
+ }
107
+ /**
108
+ * Decode an NVS partition binary into structured namespaces.
109
+ *
110
+ * Handles V1 (single-page blobs) and V2 (multi-page blobs).
111
+ * Does not support encrypted partitions.
112
+ */
113
+ function decodeNVS(data) {
114
+ const buf = data instanceof Uint8Array ? data : new Uint8Array(data);
115
+ if (buf.length % 4096 !== 0) throw new Error(`Partition data length ${buf.length} is not aligned to page size ${PAGE_SIZE}`);
116
+ const allEntries = [];
117
+ for (let pageOff = 0; pageOff < buf.length; pageOff += PAGE_SIZE) {
118
+ const page = buf.subarray(pageOff, pageOff + PAGE_SIZE);
119
+ const pageStateRaw = (page[0] | page[1] << 8 | page[2] << 16 | page[3] << 24) >>> 0;
120
+ if (pageStateRaw === 4294967295 || pageStateRaw === 0) continue;
121
+ const version = page[8];
122
+ if (version !== 255 && version !== 254) continue;
123
+ allEntries.push(...parsePage(page));
124
+ }
125
+ const nsIdxToName = /* @__PURE__ */ new Map();
126
+ for (const e of allEntries) if (e.nsIdx === 0 && e.typeCode === 1) {
127
+ const assignedIdx = e.inlineData[0];
128
+ nsIdxToName.set(assignedIdx, e.key);
129
+ }
130
+ const blobDataChunks = /* @__PURE__ */ new Map();
131
+ const blobIndex = /* @__PURE__ */ new Map();
132
+ const v1Blobs = /* @__PURE__ */ new Map();
133
+ for (const e of allEntries) {
134
+ if (e.nsIdx === 0) continue;
135
+ const bk = `${e.nsIdx}:${e.key}`;
136
+ if (e.typeCode === 66) {
137
+ if (!blobDataChunks.has(bk)) blobDataChunks.set(bk, /* @__PURE__ */ new Map());
138
+ const payload = e.childData ?? new Uint8Array(0);
139
+ blobDataChunks.get(bk).set(e.chunkIndex, payload);
140
+ } else if (e.typeCode === 72) {
141
+ const view = new DataView(e.inlineData.buffer, e.inlineData.byteOffset, 8);
142
+ blobIndex.set(bk, {
143
+ totalSize: view.getUint32(0, true),
144
+ chunkCount: e.inlineData[4],
145
+ chunkStart: e.inlineData[5]
146
+ });
147
+ } else if (e.typeCode === 65) {
148
+ const size = new DataView(e.inlineData.buffer, e.inlineData.byteOffset, 8).getUint16(0, true);
149
+ const payload = e.childData ?? new Uint8Array(0);
150
+ v1Blobs.set(bk, {
151
+ size,
152
+ data: payload
153
+ });
154
+ }
155
+ }
156
+ const result = {};
157
+ for (const e of allEntries) {
158
+ if (e.nsIdx === 0) continue;
159
+ if (e.typeCode === 66 || e.typeCode === 72) continue;
160
+ const nsName = nsIdxToName.get(e.nsIdx);
161
+ if (nsName === void 0) continue;
162
+ if (!result[nsName]) result[nsName] = [];
163
+ const bk = `${e.nsIdx}:${e.key}`;
164
+ if (PRIMITIVE_TYPES.has(e.typeCode)) {
165
+ const val = parsePrimitive(e.typeCode, e.inlineData);
166
+ const enc = e.typeCode === 1 ? "u8" : e.typeCode === 17 ? "i8" : e.typeCode === 2 ? "u16" : e.typeCode === 18 ? "i16" : e.typeCode === 4 ? "u32" : e.typeCode === 20 ? "i32" : e.typeCode === 8 ? "u64" : "i64";
167
+ result[nsName].push({
168
+ name: e.key,
169
+ encoding: enc,
170
+ value: val
171
+ });
172
+ } else if (e.typeCode === 33) {
173
+ const size = new DataView(e.inlineData.buffer, e.inlineData.byteOffset, 8).getUint16(0, true);
174
+ const payload = e.childData ?? new Uint8Array(0);
175
+ const str = new TextDecoder().decode(payload.subarray(0, Math.max(0, size - 1)));
176
+ result[nsName].push({
177
+ name: e.key,
178
+ encoding: "string",
179
+ value: str
180
+ });
181
+ } else if (e.typeCode === 65) {
182
+ const v1 = v1Blobs.get(bk);
183
+ if (v1) {
184
+ const data = v1.data.subarray(0, v1.size);
185
+ result[nsName].push({
186
+ name: e.key,
187
+ encoding: "blob_data",
188
+ value: new Uint8Array(data)
189
+ });
190
+ }
191
+ }
192
+ }
193
+ for (const [bk, chunks] of blobDataChunks) {
194
+ const [nsIdxStr, ...keyParts] = bk.split(":");
195
+ const nsIdx = parseInt(nsIdxStr);
196
+ const key = keyParts.join(":");
197
+ const nsName = nsIdxToName.get(nsIdx);
198
+ if (nsName === void 0) continue;
199
+ const idx = blobIndex.get(bk);
200
+ const allChunkData = [...chunks.keys()].sort((a, b) => a - b).map((k) => chunks.get(k));
201
+ const totalRaw = allChunkData.reduce((acc, c) => acc + c.length, 0);
202
+ const assembled = new Uint8Array(totalRaw);
203
+ let off = 0;
204
+ for (const c of allChunkData) {
205
+ assembled.set(c, off);
206
+ off += c.length;
207
+ }
208
+ const trimmed = idx ? assembled.subarray(0, idx.totalSize) : assembled;
209
+ if (!result[nsName]) result[nsName] = [];
210
+ result[nsName].push({
211
+ name: key,
212
+ encoding: "blob_data",
213
+ value: new Uint8Array(trimmed)
214
+ });
215
+ }
216
+ return result;
217
+ }
218
+ //#endregion
219
+ Object.defineProperty(exports, "ENCODING_TO_TYPE_CODE", {
220
+ enumerable: true,
221
+ get: function() {
222
+ return ENCODING_TO_TYPE_CODE;
223
+ }
224
+ });
225
+ Object.defineProperty(exports, "PAGE_SIZE", {
226
+ enumerable: true,
227
+ get: function() {
228
+ return PAGE_SIZE;
229
+ }
230
+ });
231
+ Object.defineProperty(exports, "PAGE_STATE_ACTIVE", {
232
+ enumerable: true,
233
+ get: function() {
234
+ return PAGE_STATE_ACTIVE;
235
+ }
236
+ });
237
+ Object.defineProperty(exports, "PAGE_STATE_FULL", {
238
+ enumerable: true,
239
+ get: function() {
240
+ return PAGE_STATE_FULL;
241
+ }
242
+ });
243
+ Object.defineProperty(exports, "decodeNVS", {
244
+ enumerable: true,
245
+ get: function() {
246
+ return decodeNVS;
247
+ }
248
+ });
@@ -0,0 +1,56 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ const require_decoder = require("./decoder.cjs");
25
+ let node_fs = require("node:fs");
26
+ node_fs = __toESM(node_fs, 1);
27
+ let node_path = require("node:path");
28
+ node_path = __toESM(node_path, 1);
29
+ //#region src/cli/esp-nvs-decode.ts
30
+ function printUsage() {
31
+ console.error("Usage: esp-nvs-decode <partition.bin>");
32
+ console.error(" npm run decode -- <partition.bin>");
33
+ }
34
+ function toJsonFriendly(value) {
35
+ if (value instanceof Uint8Array) return Buffer.from(value).toString("base64");
36
+ if (typeof value === "bigint") return value.toString();
37
+ if (Array.isArray(value)) return value.map((item) => toJsonFriendly(item));
38
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, toJsonFriendly(item)]));
39
+ return value ?? null;
40
+ }
41
+ function main(args) {
42
+ const inputPath = args[0];
43
+ if (!inputPath || inputPath === "-h" || inputPath === "--help") {
44
+ printUsage();
45
+ process.exit(inputPath ? 0 : 1);
46
+ }
47
+ const absoluteInputPath = node_path.resolve(process.cwd(), inputPath);
48
+ if (!node_fs.existsSync(absoluteInputPath)) {
49
+ console.error(`Input file not found: ${absoluteInputPath}`);
50
+ process.exit(1);
51
+ }
52
+ const decoded = require_decoder.decodeNVS(node_fs.readFileSync(absoluteInputPath));
53
+ process.stdout.write(`${JSON.stringify(toJsonFriendly(decoded), null, 2)}\n`);
54
+ }
55
+ //#endregion
56
+ exports.main = main;
@@ -0,0 +1,278 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_decoder = require("./decoder.cjs");
3
+ //#region src/crc.ts
4
+ /**
5
+ * Pure CRC-32 implementation compatible with zlib.crc32 (polynomial 0xEDB88320).
6
+ * Browser-safe: no Node.js dependencies.
7
+ */
8
+ const CRC32_TABLE = (() => {
9
+ const table = new Uint32Array(256);
10
+ for (let i = 0; i < 256; i++) {
11
+ let c = i;
12
+ for (let j = 0; j < 8; j++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
13
+ table[i] = c;
14
+ }
15
+ return table;
16
+ })();
17
+ /**
18
+ * Compute CRC-32 of data, matching Python's `zlib.crc32(data, seed)`.
19
+ * The default seed of 0xFFFFFFFF matches the NVS usage pattern.
20
+ */
21
+ function crc32(data, seed = 4294967295) {
22
+ let crc = seed >>> 0;
23
+ for (let i = 0; i < data.length; i++) crc = CRC32_TABLE[(crc ^ data[i]) & 255] ^ crc >>> 8;
24
+ return crc >>> 0;
25
+ }
26
+ //#endregion
27
+ //#region src/encoder.ts
28
+ var PageFullError = class extends Error {
29
+ constructor() {
30
+ super("Page is full");
31
+ }
32
+ };
33
+ /** A single 4096-byte NVS page being written. */
34
+ var PageWriter = class {
35
+ constructor(pageNum) {
36
+ this.entryNum = 0;
37
+ this.buf = new Uint8Array(require_decoder.PAGE_SIZE).fill(255);
38
+ this.view = new DataView(this.buf.buffer);
39
+ this.initHeader(pageNum);
40
+ this.initBitmap();
41
+ }
42
+ initHeader(pageNum) {
43
+ this.view.setUint32(0, require_decoder.PAGE_STATE_ACTIVE, true);
44
+ this.view.setUint32(4, pageNum, true);
45
+ this.buf[8] = 254;
46
+ const checksum = crc32(this.buf.subarray(4, 28));
47
+ this.view.setUint32(28, checksum, true);
48
+ }
49
+ initBitmap() {
50
+ this.buf.subarray(32, 64).fill(255);
51
+ }
52
+ markBitmapWritten(idx) {
53
+ const bitNum = idx * 2;
54
+ const byteIdx = 32 + (bitNum >> 3);
55
+ const bitOffset = bitNum & 7;
56
+ this.buf[byteIdx] &= ~(1 << bitOffset);
57
+ }
58
+ hasRoom(span) {
59
+ return this.entryNum + span <= 126;
60
+ }
61
+ /**
62
+ * Write `span` 32-byte chunks starting from entryNum.
63
+ * First chunk is the entry header; subsequent ones are data chunks.
64
+ */
65
+ writeRawEntry(header, dataChunks) {
66
+ const span = 1 + (dataChunks?.length ?? 0);
67
+ if (!this.hasRoom(span)) throw new PageFullError();
68
+ const off = 64 + this.entryNum * 32;
69
+ this.buf.set(header.subarray(0, 32), off);
70
+ this.markBitmapWritten(this.entryNum++);
71
+ if (dataChunks) for (const chunk of dataChunks) {
72
+ const chunkOff = 64 + this.entryNum * 32;
73
+ this.buf.set(chunk.subarray(0, Math.min(32, chunk.length)), chunkOff);
74
+ this.markBitmapWritten(this.entryNum++);
75
+ }
76
+ }
77
+ markFull() {
78
+ this.view.setUint32(0, require_decoder.PAGE_STATE_FULL, true);
79
+ }
80
+ getData() {
81
+ return this.buf;
82
+ }
83
+ };
84
+ function buildEntryHeader(nsIdx, typeCode, span, chunkIndex, key) {
85
+ const header = new Uint8Array(32).fill(255);
86
+ header[0] = nsIdx;
87
+ header[1] = typeCode;
88
+ header[2] = span;
89
+ header[3] = chunkIndex;
90
+ header.subarray(8, 24).fill(0);
91
+ const keyBytes = new TextEncoder().encode(key);
92
+ header.set(keyBytes.subarray(0, 15), 8);
93
+ return header;
94
+ }
95
+ function computeAndSetHeaderCrc(header) {
96
+ const crcData = new Uint8Array(28);
97
+ crcData.set(header.subarray(0, 4), 0);
98
+ crcData.set(header.subarray(8, 32), 4);
99
+ const checksum = crc32(crcData);
100
+ new DataView(header.buffer, header.byteOffset).setUint32(4, checksum, true);
101
+ }
102
+ /** Split data into 32-byte-aligned chunks for NVS storage. */
103
+ function toDataChunks(data) {
104
+ const chunks = [];
105
+ const roundedSize = data.length + 31 & -32;
106
+ const padded = new Uint8Array(roundedSize).fill(255);
107
+ padded.set(data);
108
+ for (let i = 0; i < roundedSize; i += 32) chunks.push(padded.subarray(i, i + 32));
109
+ return chunks;
110
+ }
111
+ /** NVS partition writer — manages pages and entry placement. */
112
+ var NVSWriter = class {
113
+ constructor() {
114
+ this.pages = [];
115
+ this.pageNum = 0;
116
+ this.curPage = this.newPage();
117
+ }
118
+ newPage() {
119
+ const p = new PageWriter(this.pageNum++);
120
+ this.pages.push(p);
121
+ return p;
122
+ }
123
+ ensureRoom(span) {
124
+ if (!this.curPage.hasRoom(span)) {
125
+ this.curPage.markFull();
126
+ this.curPage = this.newPage();
127
+ }
128
+ }
129
+ writeEntry(header, dataChunks) {
130
+ const span = 1 + (dataChunks?.length ?? 0);
131
+ this.ensureRoom(span);
132
+ this.curPage.writeRawEntry(header, dataChunks);
133
+ }
134
+ /** Write a namespace entry at ns_idx=0. Returns the namespace index assigned. */
135
+ writeNamespace(key, nsIdx) {
136
+ const header = buildEntryHeader(0, 1, 1, 255, key);
137
+ new DataView(header.buffer, header.byteOffset).setUint8(24, nsIdx);
138
+ computeAndSetHeaderCrc(header);
139
+ this.writeEntry(header);
140
+ }
141
+ /** Write a primitive-encoded entry. */
142
+ writePrimitive(nsIdx, key, encoding, value) {
143
+ const typeCode = require_decoder.ENCODING_TO_TYPE_CODE[encoding];
144
+ if (typeCode === void 0) throw new Error(`Unknown encoding: ${encoding}`);
145
+ const header = buildEntryHeader(nsIdx, typeCode, 1, 255, key);
146
+ const view = new DataView(header.buffer, header.byteOffset);
147
+ switch (encoding) {
148
+ case "u8":
149
+ view.setUint8(24, Number(value));
150
+ break;
151
+ case "i8":
152
+ view.setInt8(24, Number(value));
153
+ break;
154
+ case "u16":
155
+ view.setUint16(24, Number(value), true);
156
+ break;
157
+ case "i16":
158
+ view.setInt16(24, Number(value), true);
159
+ break;
160
+ case "u32":
161
+ view.setUint32(24, Number(value), true);
162
+ break;
163
+ case "i32":
164
+ view.setInt32(24, Number(value), true);
165
+ break;
166
+ case "u64":
167
+ view.setBigUint64(24, BigInt(value), true);
168
+ break;
169
+ case "i64":
170
+ view.setBigInt64(24, BigInt(value), true);
171
+ break;
172
+ }
173
+ computeAndSetHeaderCrc(header);
174
+ this.writeEntry(header);
175
+ }
176
+ /** Write a string entry (NUL-terminated). */
177
+ writeString(nsIdx, key, value) {
178
+ const encoded = new TextEncoder().encode(value);
179
+ const data = new Uint8Array(encoded.length + 1);
180
+ data.set(encoded);
181
+ data[encoded.length] = 0;
182
+ const chunks = toDataChunks(data);
183
+ const header = buildEntryHeader(nsIdx, 33, 1 + chunks.length, 255, key);
184
+ const hView = new DataView(header.buffer, header.byteOffset);
185
+ hView.setUint16(24, data.length, true);
186
+ const payloadCrc = crc32(data);
187
+ hView.setUint32(28, payloadCrc, true);
188
+ computeAndSetHeaderCrc(header);
189
+ this.writeEntry(header, chunks);
190
+ }
191
+ /** Write a blob (binary data) as V2 blob_data chunks + blob_index. */
192
+ writeBlobV2(nsIdx, key, data) {
193
+ let chunkStart = 0;
194
+ let chunkCount = 0;
195
+ let offset = 0;
196
+ let remainingSize = data.length;
197
+ while (remainingSize > 0 || chunkCount === 0) {
198
+ const tailroomBytes = (126 - this.curPage.entryNum - 1) * 32;
199
+ let chunkSize;
200
+ if (tailroomBytes < remainingSize) chunkSize = tailroomBytes;
201
+ else chunkSize = remainingSize;
202
+ if (chunkSize < 0) chunkSize = 0;
203
+ const chunkData = data.subarray(offset, offset + chunkSize);
204
+ const chunks = toDataChunks(chunkData);
205
+ const header = buildEntryHeader(nsIdx, 66, 1 + chunks.length, chunkStart + chunkCount, key);
206
+ const hView = new DataView(header.buffer, header.byteOffset);
207
+ hView.setUint16(24, chunkSize, true);
208
+ const payloadCrc = crc32(chunkData.length > 0 ? chunkData : new Uint8Array(0));
209
+ hView.setUint32(28, payloadCrc, true);
210
+ computeAndSetHeaderCrc(header);
211
+ this.writeEntry(header, chunks);
212
+ chunkCount++;
213
+ offset += chunkSize;
214
+ remainingSize -= chunkSize;
215
+ if (remainingSize > 0) {
216
+ this.curPage.markFull();
217
+ this.curPage = this.newPage();
218
+ }
219
+ if (chunkSize === 0 && remainingSize === 0) break;
220
+ }
221
+ const idxHeader = buildEntryHeader(nsIdx, 72, 1, 255, key);
222
+ new DataView(idxHeader.buffer, idxHeader.byteOffset).setUint32(24, data.length, true);
223
+ idxHeader[28] = chunkCount;
224
+ idxHeader[29] = chunkStart;
225
+ computeAndSetHeaderCrc(idxHeader);
226
+ this.writeEntry(idxHeader);
227
+ }
228
+ getData() {
229
+ const totalSize = this.pages.length * require_decoder.PAGE_SIZE;
230
+ const result = new Uint8Array(totalSize);
231
+ for (let i = 0; i < this.pages.length; i++) result.set(this.pages[i].getData(), i * require_decoder.PAGE_SIZE);
232
+ return result;
233
+ }
234
+ };
235
+ /**
236
+ * Encode an NVSNamespaces object into a binary NVS partition.
237
+ * Output is always V2 (multipage blob support enabled).
238
+ * Partition size is auto-calculated based on data volume.
239
+ */
240
+ function encodeNVS(namespaces) {
241
+ const writer = new NVSWriter();
242
+ const nsMap = /* @__PURE__ */ new Map();
243
+ let nextNsIdx = 1;
244
+ for (const [nsName, values] of Object.entries(namespaces)) {
245
+ let nsIdx = nsMap.get(nsName);
246
+ if (nsIdx === void 0) {
247
+ nsIdx = nextNsIdx++;
248
+ nsMap.set(nsName, nsIdx);
249
+ writer.writeNamespace(nsName, nsIdx);
250
+ }
251
+ for (const entry of values) {
252
+ const { name: key, encoding, value } = entry;
253
+ switch (encoding) {
254
+ case "u8":
255
+ case "i8":
256
+ case "u16":
257
+ case "i16":
258
+ case "u32":
259
+ case "i32":
260
+ case "u64":
261
+ case "i64":
262
+ writer.writePrimitive(nsIdx, key, encoding, value);
263
+ break;
264
+ case "string":
265
+ writer.writeString(nsIdx, key, value);
266
+ break;
267
+ case "blob_data":
268
+ writer.writeBlobV2(nsIdx, key, value);
269
+ break;
270
+ default: throw new Error(`Unsupported encoding: ${encoding}`);
271
+ }
272
+ }
273
+ }
274
+ return writer.getData();
275
+ }
276
+ //#endregion
277
+ exports.decodeNVS = require_decoder.decodeNVS;
278
+ exports.encodeNVS = encodeNVS;