node-ctypes 0.1.5 → 1.0.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.

Potentially problematic release.


This version of node-ctypes might be problematic. Click here for more details.

@@ -0,0 +1,925 @@
1
+ /**
2
+ * @file struct.js
3
+ * @module structures/struct
4
+ * @description Struct type definition implementation for C-style structures.
5
+ *
6
+ * This module provides Python ctypes-compatible struct types with proper
7
+ * alignment, padding, nested structures, arrays, and bitfields support.
8
+ *
9
+ * **Python ctypes Compatibility**:
10
+ * Similar to Python's `ctypes.Structure` class with `_fields_` definition.
11
+ *
12
+ * @example Basic struct
13
+ * ```javascript
14
+ * import { struct, c_int32, c_float } from 'node-ctypes';
15
+ *
16
+ * const Point = struct({
17
+ * x: c_int32,
18
+ * y: c_int32
19
+ * });
20
+ *
21
+ * const point = Point.create({ x: 10, y: 20 });
22
+ * console.log(point.x, point.y); // 10 20
23
+ * ```
24
+ *
25
+ * @example Struct with nested types and bitfields
26
+ * ```javascript
27
+ * const Header = struct({
28
+ * magic: c_uint32,
29
+ * flags: bitfield(c_uint32, 8),
30
+ * version: bitfield(c_uint32, 8),
31
+ * reserved: bitfield(c_uint32, 16)
32
+ * });
33
+ * ```
34
+ */
35
+
36
+ import { _readBitField, _writeBitField } from "./bitfield.js";
37
+
38
+ /**
39
+ * Creates a struct type definition with proper alignment and padding.
40
+ *
41
+ * Returns a struct definition object with methods to create instances,
42
+ * read/write fields, and convert to/from JavaScript objects. Handles
43
+ * alignment, padding, nested structures, arrays, and bitfields.
44
+ *
45
+ * **Python ctypes Compatibility**:
46
+ * Similar to Python's `Structure` class with `_fields_` attribute.
47
+ *
48
+ * @param {Object} fields - Field definitions { name: type, ... }
49
+ * @param {Object} [options] - Options { packed: false }
50
+ * @param {boolean} [options.packed=false] - If true, disable padding/alignment
51
+ * @param {Function} sizeof - sizeof function reference
52
+ * @param {Function} alloc - alloc function reference
53
+ * @param {Function} readValue - readValue function reference
54
+ * @param {Function} writeValue - writeValue function reference
55
+ * @param {Function} _isStruct - _isStruct function reference
56
+ * @param {Function} _isArrayType - _isArrayType function reference
57
+ * @param {Function} _isBitField - _isBitField function reference
58
+ * @param {Function} Structure - Structure class reference
59
+ * @param {Function} Union - Union class reference
60
+ * @param {Object} native - Native module reference
61
+ * @returns {Object} Struct definition with create, get, set, toObject methods
62
+ * @throws {TypeError} If fields are invalid or field names are duplicated
63
+ *
64
+ * @example Create struct with alignment
65
+ * ```javascript
66
+ * const Data = struct({
67
+ * a: c_uint8, // offset 0, size 1
68
+ * // padding: 3 bytes for alignment
69
+ * b: c_uint32, // offset 4, size 4
70
+ * c: c_uint16 // offset 8, size 2
71
+ * });
72
+ * console.log(Data.size); // 12 (with padding)
73
+ * ```
74
+ *
75
+ * @example Packed struct (no padding)
76
+ * ```javascript
77
+ * const PackedData = struct({
78
+ * a: c_uint8, // offset 0
79
+ * b: c_uint32, // offset 1
80
+ * c: c_uint16 // offset 5
81
+ * }, { packed: true });
82
+ * console.log(PackedData.size); // 7 (no padding)
83
+ * ```
84
+ */
85
+ export function struct(fields, options = {}, sizeof, alloc, readValue, writeValue, _isStruct, _isArrayType, _isBitField, Structure, Union, native) {
86
+ // Validate inputs
87
+ if (!fields || typeof fields !== "object" || Array.isArray(fields)) {
88
+ throw new TypeError("fields must be a non-null object");
89
+ }
90
+
91
+ const packed = options.packed || false;
92
+ let totalSize = 0;
93
+ const fieldDefs = [];
94
+ let maxAlignment = 1;
95
+
96
+ // Track field names to detect duplicates
97
+ const fieldNames = new Set();
98
+
99
+ // Stato per bit fields consecutivi
100
+ let currentBitFieldBase = null;
101
+ let currentBitFieldOffset = 0;
102
+ let currentBitOffset = 0;
103
+
104
+ for (const [name, type] of Object.entries(fields)) {
105
+ // Validate field name
106
+ if (!name || typeof name !== "string" || name.trim().length === 0) {
107
+ throw new TypeError("Field name must be a non-empty string");
108
+ }
109
+ if (fieldNames.has(name)) {
110
+ throw new TypeError(`Duplicate field name: ${name}`);
111
+ }
112
+ fieldNames.add(name);
113
+ let fieldDef;
114
+
115
+ // Caso 0: Anonymous field - { type: SomeStruct, anonymous: true }
116
+ if (typeof type === "object" && type !== null && type.anonymous === true && type.type) {
117
+ // Reset bit field state
118
+ currentBitFieldBase = null;
119
+ currentBitOffset = 0;
120
+
121
+ const actualType = type.type;
122
+ const size = actualType.size;
123
+ const alignment = packed ? 1 : actualType.alignment;
124
+
125
+ // Applica padding per allineamento
126
+ if (!packed && totalSize % alignment !== 0) {
127
+ totalSize += alignment - (totalSize % alignment);
128
+ }
129
+
130
+ fieldDef = {
131
+ name,
132
+ type: actualType,
133
+ offset: totalSize,
134
+ size,
135
+ alignment,
136
+ isNested: true,
137
+ isAnonymous: true,
138
+ };
139
+
140
+ totalSize += size;
141
+
142
+ if (alignment > maxAlignment) {
143
+ maxAlignment = alignment;
144
+ }
145
+ }
146
+ // Caso 1: Bit field
147
+ else if (_isBitField(type)) {
148
+ const { baseType, bits, baseSize } = type;
149
+ const alignment = packed ? 1 : Math.min(baseSize, native.POINTER_SIZE);
150
+
151
+ // Verifica se possiamo continuare nel bit field corrente
152
+ const canContinue = currentBitFieldBase === baseType && currentBitOffset + bits <= baseSize * 8;
153
+
154
+ if (!canContinue) {
155
+ // Inizia un nuovo bit field
156
+ // Applica padding per allineamento
157
+ if (!packed && totalSize % alignment !== 0) {
158
+ totalSize += alignment - (totalSize % alignment);
159
+ }
160
+
161
+ currentBitFieldBase = baseType;
162
+ currentBitFieldOffset = totalSize;
163
+ currentBitOffset = 0;
164
+ totalSize += baseSize;
165
+ }
166
+
167
+ fieldDef = {
168
+ name,
169
+ type: baseType,
170
+ offset: currentBitFieldOffset,
171
+ size: 0, // Bit fields non aggiungono size extra
172
+ alignment,
173
+ isBitField: true,
174
+ bitOffset: currentBitOffset,
175
+ bitSize: bits,
176
+ baseSize,
177
+ };
178
+
179
+ currentBitOffset += bits;
180
+
181
+ if (alignment > maxAlignment) {
182
+ maxAlignment = alignment;
183
+ }
184
+ }
185
+ // Caso 2a: Struct class (declarata come `class X extends Structure`)
186
+ else if (typeof type === "function" && type.prototype instanceof Structure) {
187
+ // Reset bit field state
188
+ currentBitFieldBase = null;
189
+ currentBitOffset = 0;
190
+
191
+ const nestedStruct = type._structDef || type._buildStruct();
192
+ const size = nestedStruct.size;
193
+ const alignment = packed ? 1 : nestedStruct.alignment;
194
+
195
+ // Applica padding per allineamento
196
+ if (!packed && totalSize % alignment !== 0) {
197
+ totalSize += alignment - (totalSize % alignment);
198
+ }
199
+
200
+ fieldDef = {
201
+ name,
202
+ type: nestedStruct,
203
+ offset: totalSize,
204
+ size,
205
+ alignment,
206
+ isNested: true,
207
+ };
208
+
209
+ totalSize += size;
210
+
211
+ if (alignment > maxAlignment) {
212
+ maxAlignment = alignment;
213
+ }
214
+ }
215
+ // Caso 2b: Union class (declarata come `class X extends Union`)
216
+ else if (typeof type === "function" && type.prototype instanceof Union) {
217
+ // Reset bit field state
218
+ currentBitFieldBase = null;
219
+ currentBitOffset = 0;
220
+
221
+ const nestedUnion = type._unionDef || type._buildUnion();
222
+ const size = nestedUnion.size;
223
+ const alignment = packed ? 1 : nestedUnion.alignment;
224
+
225
+ // Applica padding per allineamento
226
+ if (!packed && totalSize % alignment !== 0) {
227
+ totalSize += alignment - (totalSize % alignment);
228
+ }
229
+
230
+ fieldDef = {
231
+ name,
232
+ type: nestedUnion,
233
+ offset: totalSize,
234
+ size,
235
+ alignment,
236
+ isNested: true,
237
+ };
238
+
239
+ totalSize += size;
240
+
241
+ if (alignment > maxAlignment) {
242
+ maxAlignment = alignment;
243
+ }
244
+ }
245
+ // Caso 2: Nested struct
246
+ else if (_isStruct(type)) {
247
+ // Reset bit field state
248
+ currentBitFieldBase = null;
249
+ currentBitOffset = 0;
250
+
251
+ const nestedStruct = type;
252
+ const size = nestedStruct.size;
253
+ const alignment = packed ? 1 : nestedStruct.alignment;
254
+
255
+ // Applica padding per allineamento
256
+ if (!packed && totalSize % alignment !== 0) {
257
+ totalSize += alignment - (totalSize % alignment);
258
+ }
259
+
260
+ fieldDef = {
261
+ name,
262
+ type: nestedStruct,
263
+ offset: totalSize,
264
+ size,
265
+ alignment,
266
+ isNested: true,
267
+ };
268
+
269
+ totalSize += size;
270
+
271
+ if (alignment > maxAlignment) {
272
+ maxAlignment = alignment;
273
+ }
274
+ }
275
+ // Caso 3: Array type
276
+ else if (_isArrayType(type)) {
277
+ // Reset bit field state
278
+ currentBitFieldBase = null;
279
+ currentBitOffset = 0;
280
+
281
+ const size = type.getSize();
282
+ const elemSize = sizeof(type.elementType);
283
+ const alignment = packed ? 1 : Math.min(elemSize, native.POINTER_SIZE);
284
+
285
+ // Applica padding per allineamento
286
+ if (!packed && totalSize % alignment !== 0) {
287
+ totalSize += alignment - (totalSize % alignment);
288
+ }
289
+
290
+ fieldDef = {
291
+ name,
292
+ type,
293
+ offset: totalSize,
294
+ size,
295
+ alignment,
296
+ isArray: true,
297
+ };
298
+
299
+ totalSize += size;
300
+
301
+ if (alignment > maxAlignment) {
302
+ maxAlignment = alignment;
303
+ }
304
+ }
305
+ // Caso 4: Tipo base (SimpleCData)
306
+ else {
307
+ // Reset bit field state
308
+ currentBitFieldBase = null;
309
+ currentBitOffset = 0;
310
+
311
+ // Validate type is a SimpleCData class
312
+ if (!(typeof type === "function" && type._isSimpleCData)) {
313
+ throw new TypeError(`struct field "${name}": type must be a SimpleCData class, struct, union, or array`);
314
+ }
315
+
316
+ const size = type._size;
317
+ const naturalAlign = Math.min(size, native.POINTER_SIZE);
318
+ const alignment = packed ? 1 : naturalAlign;
319
+
320
+ // Applica padding per allineamento
321
+ if (!packed && totalSize % alignment !== 0) {
322
+ totalSize += alignment - (totalSize % alignment);
323
+ }
324
+
325
+ fieldDef = {
326
+ name,
327
+ type,
328
+ offset: totalSize,
329
+ size,
330
+ alignment,
331
+ };
332
+
333
+ totalSize += size;
334
+
335
+ if (alignment > maxAlignment) {
336
+ maxAlignment = alignment;
337
+ }
338
+ }
339
+
340
+ fieldDefs.push(fieldDef);
341
+ }
342
+
343
+ // Padding finale per allineamento della struct
344
+ if (!packed && totalSize % maxAlignment !== 0) {
345
+ totalSize += maxAlignment - (totalSize % maxAlignment);
346
+ }
347
+
348
+ // Crea Map per lookup O(1) invece di find() O(n)
349
+ const fieldMap = new Map();
350
+ for (const field of fieldDefs) {
351
+ fieldMap.set(field.name, field);
352
+ }
353
+
354
+ // Pre-compile reader/writer functions for each field
355
+ const fieldReaders = new Map();
356
+ const fieldWriters = new Map();
357
+
358
+ for (const field of fieldDefs) {
359
+ const offset = field.offset;
360
+ const name = field.name;
361
+
362
+ // Skip complex types - they need special handling
363
+ if (field.isBitField || field.isNested || field.isArray) {
364
+ continue;
365
+ }
366
+
367
+ // Pre-compile based on SimpleCData type
368
+ // Use the type's _reader and _writer directly for optimization
369
+ const fieldType = field.type;
370
+ if (fieldType && fieldType._isSimpleCData) {
371
+ const fieldOffset = offset; // Capture offset for closure
372
+ fieldReaders.set(name, (buf) => fieldType._reader(buf, fieldOffset));
373
+ fieldWriters.set(name, (buf, val) => fieldType._writer(buf, fieldOffset, val));
374
+ }
375
+ }
376
+
377
+ const structDef = {
378
+ size: totalSize,
379
+ alignment: maxAlignment,
380
+ fields: fieldDefs,
381
+ packed: packed,
382
+ _isStructType: true,
383
+
384
+ /**
385
+ * Alloca e inizializza una nuova istanza
386
+ * @param {Object} [values] - Valori iniziali
387
+ * @returns {Proxy} Proxy-based struct instance
388
+ */
389
+ create(values = {}) {
390
+ const buf = alloc(totalSize);
391
+ buf.fill(0); // Inizializza a zero
392
+
393
+ // Prima imposta i campi diretti
394
+ for (const field of fieldDefs) {
395
+ if (values[field.name] !== undefined) {
396
+ structDef.set(buf, field.name, values[field.name]);
397
+ }
398
+ }
399
+
400
+ // Poi gestisci i campi anonymous - i loro campi possono essere passati direttamente nell'oggetto values
401
+ for (const field of fieldDefs) {
402
+ if (field.isAnonymous && field.type && field.type.fields) {
403
+ for (const subField of field.type.fields) {
404
+ if (values[subField.name] !== undefined) {
405
+ structDef.set(buf, subField.name, values[subField.name]);
406
+ }
407
+ }
408
+ }
409
+ }
410
+
411
+ // Proxy-based instance with pre-compiled readers/writers
412
+ return new Proxy(buf, {
413
+ get(target, prop, receiver) {
414
+ // Special properties
415
+ if (prop === "_buffer") return target;
416
+ if (prop === "toObject") return () => structDef.toObject(target);
417
+ if (prop === Symbol.toStringTag) return "StructInstance";
418
+ if (prop === Symbol.iterator) return undefined;
419
+
420
+ // Handle Buffer/TypedArray properties FIRST before field checks
421
+ // TypedArray methods need direct access to avoid Proxy interference
422
+ if (prop === "length" || prop === "byteLength" || prop === "byteOffset" || prop === "buffer" || prop === "BYTES_PER_ELEMENT") {
423
+ return target[prop];
424
+ }
425
+
426
+ // Use pre-compiled reader if available
427
+ if (typeof prop === "string") {
428
+ const reader = fieldReaders.get(prop);
429
+ if (reader) return reader(target);
430
+
431
+ // Fallback to general get for complex types
432
+ if (fieldMap.has(prop)) {
433
+ return structDef.get(target, prop);
434
+ }
435
+ }
436
+
437
+ // Check anonymous fields
438
+ for (const f of fieldDefs) {
439
+ if (f.isAnonymous && f.type && f.type.fields) {
440
+ if (f.type.fields.some((sf) => sf.name === prop)) {
441
+ return structDef.get(target, prop);
442
+ }
443
+ }
444
+ }
445
+
446
+ // Fallback to buffer properties and methods
447
+ const value = target[prop];
448
+ if (typeof value === "function") {
449
+ return value.bind(target);
450
+ }
451
+ return value;
452
+ },
453
+ set(target, prop, value, receiver) {
454
+ // FAST PATH: Use pre-compiled writer if available
455
+ if (typeof prop === "string") {
456
+ const writer = fieldWriters.get(prop);
457
+ if (writer) {
458
+ writer(target, value);
459
+ return true;
460
+ }
461
+
462
+ // Fallback to general set for complex types
463
+ if (fieldMap.has(prop)) {
464
+ structDef.set(target, prop, value);
465
+ return true;
466
+ }
467
+ }
468
+
469
+ // Check anonymous fields
470
+ for (const f of fieldDefs) {
471
+ if (f.isAnonymous && f.type && f.type.fields) {
472
+ if (f.type.fields.some((sf) => sf.name === prop)) {
473
+ structDef.set(target, prop, value);
474
+ return true;
475
+ }
476
+ }
477
+ }
478
+
479
+ // Fallback
480
+ return Reflect.set(target, prop, value, target);
481
+ },
482
+ has(target, prop) {
483
+ if (prop === "_buffer" || prop === "toObject") return true;
484
+ if (typeof prop === "string" && fieldMap.has(prop)) return true;
485
+ // Check anonymous fields
486
+ for (const f of fieldDefs) {
487
+ if (f.isAnonymous && f.type && f.type.fields) {
488
+ if (f.type.fields.some((sf) => sf.name === prop)) return true;
489
+ }
490
+ }
491
+ return Reflect.has(target, prop);
492
+ },
493
+ ownKeys(target) {
494
+ const keys = [];
495
+ for (const f of fieldDefs) {
496
+ if (f.isAnonymous && f.type && f.type.fields) {
497
+ for (const sf of f.type.fields) {
498
+ keys.push(sf.name);
499
+ }
500
+ } else {
501
+ keys.push(f.name);
502
+ }
503
+ }
504
+ return keys;
505
+ },
506
+ getOwnPropertyDescriptor(target, prop) {
507
+ if (typeof prop === "string" && (fieldMap.has(prop) || prop === "_buffer" || prop === "toObject")) {
508
+ return {
509
+ enumerable: prop !== "_buffer" && prop !== "toObject",
510
+ configurable: true,
511
+ writable: true,
512
+ };
513
+ }
514
+ // Check anonymous fields
515
+ for (const f of fieldDefs) {
516
+ if (f.isAnonymous && f.type && f.type.fields) {
517
+ if (f.type.fields.some((sf) => sf.name === prop)) {
518
+ return {
519
+ enumerable: true,
520
+ configurable: true,
521
+ writable: true,
522
+ };
523
+ }
524
+ }
525
+ }
526
+ return Reflect.getOwnPropertyDescriptor(target, prop);
527
+ },
528
+ });
529
+ },
530
+
531
+ /**
532
+ * Legge un campo dalla struttura
533
+ * @param {Buffer|Object} bufOrObj - Buffer della struttura O object wrapper
534
+ * @param {string} fieldName - Nome del campo (supporta 'outer.inner' per nested)
535
+ * @returns {*} Valore del campo
536
+ */
537
+ get(bufOrObj, fieldName) {
538
+ // supporta sia buffer che object wrapper o plain object
539
+ let buf;
540
+ if (Buffer.isBuffer(bufOrObj)) {
541
+ buf = bufOrObj;
542
+ } else if (bufOrObj && bufOrObj._buffer) {
543
+ // Object wrapper - gestisci dot notation
544
+ if (fieldName.includes(".")) {
545
+ const parts = fieldName.split(".");
546
+ let current = bufOrObj;
547
+ for (const part of parts) {
548
+ current = current[part];
549
+ if (current === undefined) return undefined;
550
+ }
551
+ return current;
552
+ }
553
+ // Accesso diretto tramite property
554
+ return bufOrObj[fieldName];
555
+ } else if (typeof bufOrObj === "object" && bufOrObj !== null) {
556
+ // Plain object (da toObject/create) - accesso diretto
557
+ if (fieldName.includes(".")) {
558
+ const parts = fieldName.split(".");
559
+ let current = bufOrObj;
560
+ for (const part of parts) {
561
+ current = current[part];
562
+ if (current === undefined) return undefined;
563
+ }
564
+ return current;
565
+ }
566
+ // Accesso diretto tramite property
567
+ return bufOrObj[fieldName];
568
+ } else {
569
+ throw new TypeError("Expected Buffer or struct instance");
570
+ }
571
+
572
+ // Fast path: nome semplice senza dot notation
573
+ // Lookup with Map
574
+ let field = fieldMap.get(fieldName);
575
+
576
+ if (field) {
577
+ // Bit field
578
+ if (field.isBitField) {
579
+ return _readBitField(buf, field.offset, field.type, field.bitOffset, field.bitSize, sizeof);
580
+ }
581
+
582
+ // Nested struct (senza dot = ritorna intero oggetto)
583
+ if (field.isNested) {
584
+ const nestedBuf = buf.subarray(field.offset, field.offset + field.size);
585
+ // Proxy-based nested instance
586
+ return field.type.create
587
+ ? // Se ha create(), usa quello per creare il proxy
588
+ new Proxy(nestedBuf, {
589
+ get(target, prop, receiver) {
590
+ if (prop === "_buffer") return target;
591
+ if (prop === "toObject") return () => field.type.toObject(target);
592
+ if (prop === Symbol.toStringTag) return "NestedStructInstance";
593
+ if (prop === Symbol.iterator) return undefined;
594
+ // Handle Buffer/TypedArray properties
595
+ if (prop === "length" || prop === "byteLength" || prop === "byteOffset" || prop === "buffer" || prop === "BYTES_PER_ELEMENT") {
596
+ return target[prop];
597
+ }
598
+ if (typeof prop === "string") {
599
+ // Check direct fields
600
+ const nestedFieldMap = field.type.fields ? new Map(field.type.fields.map((f) => [f.name, f])) : new Map();
601
+ if (nestedFieldMap.has(prop)) {
602
+ return field.type.get(target, prop);
603
+ }
604
+ // Check anonymous fields
605
+ for (const sf of field.type.fields || []) {
606
+ if (sf.isAnonymous && sf.type && sf.type.fields) {
607
+ if (sf.type.fields.some((ssf) => ssf.name === prop)) {
608
+ return field.type.get(target, prop);
609
+ }
610
+ }
611
+ }
612
+ }
613
+ const value = target[prop];
614
+ if (typeof value === "function") return value.bind(target);
615
+ return value;
616
+ },
617
+ set(target, prop, value, receiver) {
618
+ if (typeof prop === "string") {
619
+ const nestedFieldMap = field.type.fields ? new Map(field.type.fields.map((f) => [f.name, f])) : new Map();
620
+ if (nestedFieldMap.has(prop)) {
621
+ field.type.set(target, prop, value);
622
+ return true;
623
+ }
624
+ // Check anonymous fields
625
+ for (const sf of field.type.fields || []) {
626
+ if (sf.isAnonymous && sf.type && sf.type.fields) {
627
+ if (sf.type.fields.some((ssf) => ssf.name === prop)) {
628
+ field.type.set(target, prop, value);
629
+ return true;
630
+ }
631
+ }
632
+ }
633
+ }
634
+ return Reflect.set(target, prop, value, receiver);
635
+ },
636
+ has(target, prop) {
637
+ if (prop === "_buffer" || prop === "toObject") return true;
638
+ if (typeof prop === "string" && field.type.fields) {
639
+ if (field.type.fields.some((f) => f.name === prop)) return true;
640
+ }
641
+ return Reflect.has(target, prop);
642
+ },
643
+ ownKeys(target) {
644
+ return (field.type.fields || []).map((f) => f.name);
645
+ },
646
+ getOwnPropertyDescriptor(target, prop) {
647
+ if (typeof prop === "string" && field.type.fields && field.type.fields.some((f) => f.name === prop)) {
648
+ return {
649
+ enumerable: true,
650
+ configurable: true,
651
+ writable: true,
652
+ };
653
+ }
654
+ return Reflect.getOwnPropertyDescriptor(target, prop);
655
+ },
656
+ })
657
+ : field.type.toObject(nestedBuf);
658
+ }
659
+
660
+ // Array field
661
+ if (field.isArray) {
662
+ const arrayBuf = buf.subarray(field.offset, field.offset + field.size);
663
+ return field.type.wrap(arrayBuf);
664
+ }
665
+
666
+ // Tipo base - fast path!
667
+ return readValue(buf, field.type, field.offset);
668
+ }
669
+
670
+ // supporto per accesso nested 'outer.inner' o anonymous fields
671
+ const parts = fieldName.split(".");
672
+ const firstPart = parts[0];
673
+
674
+ field = fieldMap.get(firstPart);
675
+
676
+ // Se non trovato, cerca negli anonymous fields
677
+ if (!field) {
678
+ for (const f of fieldDefs) {
679
+ if (f.isAnonymous && f.type && f.type.fields) {
680
+ const hasField = f.type.fields.some((subField) => subField.name === firstPart);
681
+ if (hasField) {
682
+ const nestedBuf = buf.subarray(f.offset, f.offset + f.size);
683
+ return f.type.get(nestedBuf, fieldName);
684
+ }
685
+ }
686
+ }
687
+ throw new Error(`Unknown field: ${firstPart}`);
688
+ }
689
+
690
+ // Nested struct con dot notation
691
+ if (field.isNested && parts.length > 1) {
692
+ const nestedBuf = buf.subarray(field.offset, field.offset + field.size);
693
+ return field.type.get(nestedBuf, parts.slice(1).join("."));
694
+ }
695
+
696
+ throw new Error(`Unknown field: ${fieldName}`);
697
+ },
698
+
699
+ /**
700
+ * Scrive un campo nella struttura
701
+ * @param {Buffer|Object} bufOrObj - Buffer della struttura O object wrapper
702
+ * @param {string} fieldName - Nome del campo (supporta 'outer.inner' per nested)
703
+ * @param {*} value - Valore da scrivere
704
+ */
705
+ set(bufOrObj, fieldName, value) {
706
+ // supporta sia buffer che object wrapper o plain object
707
+ let buf;
708
+ if (Buffer.isBuffer(bufOrObj)) {
709
+ buf = bufOrObj;
710
+ } else if (bufOrObj && bufOrObj._buffer) {
711
+ // Object wrapper - accesso diretto tramite property
712
+ bufOrObj[fieldName] = value;
713
+ return;
714
+ } else if (typeof bufOrObj === "object" && bufOrObj !== null) {
715
+ // Plain object (da toObject/create) - accesso diretto
716
+ bufOrObj[fieldName] = value;
717
+ return;
718
+ } else {
719
+ throw new TypeError("Expected Buffer or struct instance");
720
+ }
721
+
722
+ // Fast path: nome semplice senza dot notation
723
+ // Lookup with Map
724
+ let field = fieldMap.get(fieldName);
725
+
726
+ if (field) {
727
+ // Bit field
728
+ if (field.isBitField) {
729
+ _writeBitField(buf, field.offset, field.type, field.bitOffset, field.bitSize, value, sizeof);
730
+ return;
731
+ }
732
+
733
+ // Nested struct (senza dot = imposta da oggetto)
734
+ if (field.isNested) {
735
+ if (Buffer.isBuffer(value)) {
736
+ value.copy(buf, field.offset, 0, field.size);
737
+ } else if (typeof value === "object") {
738
+ const nestedBuf = buf.subarray(field.offset, field.offset + field.size);
739
+ for (const [k, v] of Object.entries(value)) {
740
+ field.type.set(nestedBuf, k, v);
741
+ }
742
+ }
743
+ return;
744
+ }
745
+
746
+ // Array field
747
+ if (field.isArray) {
748
+ if (Buffer.isBuffer(value)) {
749
+ value.copy(buf, field.offset, 0, field.size);
750
+ } else if (Array.isArray(value)) {
751
+ const arrayBuf = buf.subarray(field.offset, field.offset + field.size);
752
+ const wrapped = field.type.wrap(arrayBuf);
753
+ for (let i = 0; i < Math.min(value.length, field.type.length); i++) {
754
+ wrapped[i] = value[i];
755
+ }
756
+ }
757
+ return;
758
+ }
759
+
760
+ // Tipo base - fast path!
761
+ writeValue(buf, field.type, value, field.offset);
762
+ return;
763
+ }
764
+
765
+ // Slow path: supporto per accesso nested 'outer.inner' o anonymous fields
766
+ const parts = fieldName.split(".");
767
+ const firstPart = parts[0];
768
+
769
+ field = fieldMap.get(firstPart);
770
+
771
+ // Se non trovato, cerca negli anonymous fields
772
+ if (!field) {
773
+ for (const f of fieldDefs) {
774
+ if (f.isAnonymous && f.type && f.type.fields) {
775
+ // Verifica se il campo esiste nel tipo anonimo
776
+ const hasField = f.type.fields.some((subField) => subField.name === firstPart);
777
+ if (hasField) {
778
+ // Scrive nel campo dell'anonymous field
779
+ const nestedBuf = buf.subarray(f.offset, f.offset + f.size);
780
+ f.type.set(nestedBuf, fieldName, value);
781
+ return;
782
+ }
783
+ }
784
+ }
785
+ throw new Error(`Unknown field: ${firstPart}`);
786
+ }
787
+
788
+ // Bit field
789
+ if (field.isBitField) {
790
+ _writeBitField(buf, field.offset, field.type, field.bitOffset, field.bitSize, value, sizeof);
791
+ return;
792
+ }
793
+
794
+ // Nested struct
795
+ if (field.isNested) {
796
+ if (parts.length > 1) {
797
+ // Accesso a campo annidato singolo
798
+ const nestedBuf = buf.subarray(field.offset, field.offset + field.size);
799
+ field.type.set(nestedBuf, parts.slice(1).join("."), value);
800
+ } else if (Buffer.isBuffer(value)) {
801
+ // Copia buffer
802
+ value.copy(buf, field.offset, 0, field.size);
803
+ } else if (typeof value === "object") {
804
+ // Imposta campi da oggetto
805
+ const nestedBuf = buf.subarray(field.offset, field.offset + field.size);
806
+ for (const [k, v] of Object.entries(value)) {
807
+ field.type.set(nestedBuf, k, v);
808
+ }
809
+ }
810
+ return;
811
+ }
812
+
813
+ // Array field
814
+ if (field.isArray) {
815
+ if (Buffer.isBuffer(value)) {
816
+ // Copia buffer array
817
+ value.copy(buf, field.offset, 0, field.size);
818
+ } else if (Array.isArray(value)) {
819
+ // Inizializza da array JavaScript
820
+ const arrayBuf = buf.subarray(field.offset, field.offset + field.size);
821
+ const wrapped = field.type.wrap(arrayBuf);
822
+ for (let i = 0; i < Math.min(value.length, field.type.length); i++) {
823
+ wrapped[i] = value[i];
824
+ }
825
+ }
826
+ return;
827
+ }
828
+
829
+ // Tipo base
830
+ writeValue(buf, field.type, value, field.offset);
831
+ },
832
+
833
+ /**
834
+ * Legge tutti i campi come oggetto (ricorsivo per nested)
835
+ * @param {Buffer} buf - Buffer della struttura
836
+ * @returns {Object} Oggetto con tutti i campi
837
+ */
838
+ /**
839
+ * Converte buffer in plain object (KOFFI EAGER APPROACH)
840
+ * Legge TUTTI i valori UNA VOLTA e crea PLAIN properties (no getters)
841
+ * Molto più veloce! Sincronizzazione lazy quando serve _buffer
842
+ */
843
+ toObject(bufOrObj) {
844
+ // Se è già un object (non buffer), controlla se ha _buffer
845
+ if (!Buffer.isBuffer(bufOrObj)) {
846
+ // Se ha _buffer, estrailo e ricarica i valori (utile dopo modifiche FFI)
847
+ if (bufOrObj && bufOrObj._buffer && Buffer.isBuffer(bufOrObj._buffer)) {
848
+ bufOrObj = bufOrObj._buffer; // Estrai buffer e procedi con il reload
849
+ } else if (typeof bufOrObj === "object" && bufOrObj !== null) {
850
+ // È già un plain object, restituiscilo così com'è
851
+ return bufOrObj;
852
+ } else {
853
+ throw new TypeError("Expected Buffer or struct instance");
854
+ }
855
+ }
856
+
857
+ const buf = bufOrObj;
858
+ const result = {};
859
+
860
+ // Leggi tutti i campi in una volta (eager approach)
861
+ for (const field of fieldDefs) {
862
+ if (field.isBitField) {
863
+ result[field.name] = _readBitField(buf, field.offset, field.type, field.bitOffset, field.bitSize, sizeof);
864
+ } else if (field.isNested) {
865
+ const nestedBuf = buf.subarray(field.offset, field.offset + field.size);
866
+ const nestedObj = field.type.toObject(nestedBuf);
867
+ if (field.isAnonymous) {
868
+ // Anonymous fields: promote their fields to parent level
869
+ Object.assign(result, nestedObj);
870
+ } else {
871
+ result[field.name] = nestedObj;
872
+ }
873
+ } else if (field.isArray) {
874
+ const arrayBuf = buf.subarray(field.offset, field.offset + field.size);
875
+ result[field.name] = field.type.wrap(arrayBuf);
876
+ } else {
877
+ // Tipo base - lettura diretta
878
+ result[field.name] = readValue(buf, field.type, field.offset);
879
+ }
880
+ }
881
+
882
+ return result;
883
+ },
884
+
885
+ /**
886
+ * Ottiene il buffer di una nested struct
887
+ * @param {Buffer} buf - Buffer della struttura parent
888
+ * @param {string} fieldName - Nome del campo nested
889
+ * @returns {Buffer} Slice del buffer per la nested struct
890
+ */
891
+ getNestedBuffer(buf, fieldName) {
892
+ // O(1) lookup con Map
893
+ const field = fieldMap.get(fieldName);
894
+ if (!field) throw new Error(`Unknown field: ${fieldName}`);
895
+ if (!field.isNested) throw new Error(`Field ${fieldName} is not a nested struct`);
896
+ return buf.subarray(field.offset, field.offset + field.size);
897
+ },
898
+ };
899
+
900
+ // Crea un'istanza C++ StructType per le operazioni native
901
+ try {
902
+ const cppStructType = new StructType();
903
+ // Per ora non aggiungiamo campi - testiamo se ToObject funziona senza
904
+ structDef._cppStructType = cppStructType;
905
+ } catch (e) {
906
+ // Se fallisce, continua senza C++ (fallback a JS)
907
+ console.warn("Failed to create C++ StructType, using JavaScript fallback:", e.message);
908
+ }
909
+
910
+ // fromObject: write plain object values into buffer
911
+ structDef.fromObject = function (buf, obj) {
912
+ for (const [key, value] of Object.entries(obj)) {
913
+ if (this.fields.some((f) => f.name === key)) {
914
+ this.set(buf, key, value);
915
+ }
916
+ }
917
+ };
918
+
919
+ // createRaw: return plain object without synchronization
920
+ structDef.createRaw = function (values = {}) {
921
+ return this.toObject(this.create(values)._buffer);
922
+ };
923
+
924
+ return structDef;
925
+ }