fit-file-parser 6.2.1 → 6.2.2
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/CHANGELOG.md +19 -0
- package/README.md +7 -0
- package/dist/binary.d.ts +2 -0
- package/dist/binary.js +23 -6
- package/dist/cjs/binary.d.ts +2 -0
- package/dist/cjs/binary.js +23 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 6.2.2 - 2026-10-11
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Restore ordinary decoding of existing Bryton and Magene FIT files whose
|
|
8
|
+
normal record headers set reserved bit 4. Their activity data was readable
|
|
9
|
+
before 5.2.0 and is recovered without modifying the original bytes.
|
|
10
|
+
- Require complete definitions and native/developer payloads for messages
|
|
11
|
+
using reserved-header compatibility, including ordinary data records that
|
|
12
|
+
reference a definition with the reserved bit set.
|
|
13
|
+
|
|
14
|
+
### Compatibility
|
|
15
|
+
|
|
16
|
+
- Strict parsing, full raw-message retention, and the standalone raw reader
|
|
17
|
+
continue rejecting reserved header bits. Bit 5 in data headers remains
|
|
18
|
+
invalid in all modes. Checksum behavior still follows `force`.
|
|
19
|
+
- No new options or parsed output fields. Existing successful imports need no
|
|
20
|
+
migration; retry previously rejected originals with the updated parser.
|
|
21
|
+
|
|
3
22
|
## 6.2.1 - 2026-10-10
|
|
4
23
|
|
|
5
24
|
### Added
|
package/README.md
CHANGED
|
@@ -150,6 +150,13 @@ malformed records and definitions. `parseAsync()` and callback callers keep
|
|
|
150
150
|
their current defaults, recovery, and output. `parseFilesAsync()` uses that
|
|
151
151
|
same decoder independently for each file.
|
|
152
152
|
|
|
153
|
+
Ordinary decoding tolerates the reserved bit 4 in normal record headers used
|
|
154
|
+
by some existing device files. Messages using this compatibility behavior must
|
|
155
|
+
have complete definitions and payloads inside the declared data section. Bit 5
|
|
156
|
+
in data headers remains invalid. Strict mode, full raw-message retention, and
|
|
157
|
+
the standalone raw reader continue rejecting reserved header bits. Original
|
|
158
|
+
bytes and the caller's checksum policy are unchanged.
|
|
159
|
+
|
|
153
160
|
`strict: true` adds definition and record bounds checks so CRC bytes cannot be
|
|
154
161
|
decoded as measurements. Full raw-message retention (`includeRawMessages`)
|
|
155
162
|
continues to require complete records as it did before this option was added.
|
package/dist/binary.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface MessageTypeDefinition {
|
|
|
8
8
|
fieldDefs: FieldDefinition[];
|
|
9
9
|
developerFieldDefs?: DeveloperFieldDefinition[];
|
|
10
10
|
rawData?: any[];
|
|
11
|
+
completeDefinition?: boolean;
|
|
12
|
+
reservedHeaderBit?: boolean;
|
|
11
13
|
}
|
|
12
14
|
export interface DeveloperFieldDefinition {
|
|
13
15
|
developerDataIndex: number;
|
package/dist/binary.js
CHANGED
|
@@ -417,6 +417,8 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
417
417
|
const recordHeader = blob[startIndex];
|
|
418
418
|
let localMessageType = recordHeader & 15;
|
|
419
419
|
const isCompressedTimestamp = (recordHeader & CompressedHeaderMask) === CompressedHeaderMask;
|
|
420
|
+
const reservedHeaderBit = !isCompressedTimestamp && (recordHeader & 0x10) !== 0;
|
|
421
|
+
const strictHeaders = options.strict || retainsRawMessages(options);
|
|
420
422
|
if (isCompressedTimestamp) {
|
|
421
423
|
// compressed timestamp
|
|
422
424
|
localMessageType = (recordHeader & CompressedLocalMsgNumMask) >> 5;
|
|
@@ -424,15 +426,16 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
424
426
|
else if ((recordHeader & 64) === 64) {
|
|
425
427
|
// is definition message
|
|
426
428
|
// startIndex + 1 is reserved
|
|
427
|
-
|
|
429
|
+
const requireCompleteDefinition = strictHeaders || reservedHeaderBit;
|
|
430
|
+
if (requireCompleteDefinition && startIndex + 6 > dataEnd) {
|
|
428
431
|
throw new Error('Invalid FIT definition bounds');
|
|
429
432
|
}
|
|
430
433
|
const hasDeveloperData = (recordHeader & 32) === 32;
|
|
431
434
|
const lEnd = blob[startIndex + 2] === 0;
|
|
432
435
|
const numberOfFields = blob[startIndex + 5];
|
|
433
436
|
const nativeDefinitionsEnd = startIndex + 6 + numberOfFields * 3;
|
|
434
|
-
if (
|
|
435
|
-
&& ((
|
|
437
|
+
if (requireCompleteDefinition
|
|
438
|
+
&& ((strictHeaders && reservedHeaderBit)
|
|
436
439
|
|| blob[startIndex + 1] !== 0
|
|
437
440
|
|| blob[startIndex + 2] > 1
|
|
438
441
|
|| nativeDefinitionsEnd > dataEnd
|
|
@@ -444,7 +447,7 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
444
447
|
: 0;
|
|
445
448
|
const definitionEnd = nativeDefinitionsEnd
|
|
446
449
|
+ (hasDeveloperData ? 1 + numberOfDeveloperDataFields * 3 : 0);
|
|
447
|
-
if (
|
|
450
|
+
if (requireCompleteDefinition && definitionEnd > dataEnd) {
|
|
448
451
|
throw new Error('Invalid FIT developer definition bounds');
|
|
449
452
|
}
|
|
450
453
|
const mTypeDef = {
|
|
@@ -457,6 +460,13 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
457
460
|
fieldDefs: [],
|
|
458
461
|
developerFieldDefs: [],
|
|
459
462
|
rawData: [],
|
|
463
|
+
// A later data header may use the reserved bit with an ordinary
|
|
464
|
+
// definition. Do not extend legacy partial-record recovery to that case.
|
|
465
|
+
completeDefinition: startIndex + 6 <= dataEnd
|
|
466
|
+
&& blob[startIndex + 1] === 0
|
|
467
|
+
&& blob[startIndex + 2] <= 1
|
|
468
|
+
&& definitionEnd <= dataEnd,
|
|
469
|
+
reservedHeaderBit,
|
|
460
470
|
};
|
|
461
471
|
const message = getFitMessage(mTypeDef.globalMessageNumber);
|
|
462
472
|
for (let i = 0; i < numberOfFields; i++) {
|
|
@@ -509,13 +519,20 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
509
519
|
nextIndex: hasDeveloperData ? nextIndexWithDeveloperData : nextIndex,
|
|
510
520
|
};
|
|
511
521
|
}
|
|
512
|
-
|
|
522
|
+
// Some existing producer files set bit 4 without changing the record layout.
|
|
523
|
+
// Recover only that bit in ordinary decoding; bit 5 and strict/raw parsing
|
|
524
|
+
// retain their previous rejection policy. Never rewrite the source bytes.
|
|
525
|
+
if (!isCompressedTimestamp && ((recordHeader & 0x20) !== 0 || (strictHeaders && reservedHeaderBit))) {
|
|
513
526
|
throw new Error('Invalid FIT data record header');
|
|
514
527
|
}
|
|
515
528
|
const messageType = messageTypes[localMessageType];
|
|
516
529
|
if (!messageType) {
|
|
517
530
|
throw new Error('FIT data record has no local definition');
|
|
518
531
|
}
|
|
532
|
+
const reservedHeaderCompatibility = reservedHeaderBit || messageType.reservedHeaderBit;
|
|
533
|
+
if (reservedHeaderCompatibility && !messageType.completeDefinition) {
|
|
534
|
+
throw new Error('Invalid FIT definition for reserved header compatibility');
|
|
535
|
+
}
|
|
519
536
|
let messageSize = 0;
|
|
520
537
|
let readDataFromIndex = startIndex + 1;
|
|
521
538
|
const fields = {};
|
|
@@ -530,7 +547,7 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
530
547
|
const rawDeveloperFields = includeRawDeveloperFields || includeRawMessage ? [] : undefined;
|
|
531
548
|
const unmappedFields = options.includeUnmappedMessages ? [] : undefined;
|
|
532
549
|
const unmappedDeveloperFields = options.includeUnmappedMessages ? [] : undefined;
|
|
533
|
-
if (
|
|
550
|
+
if (strictHeaders || reservedHeaderCompatibility) {
|
|
534
551
|
const nativeSize = messageType.fieldDefs.reduce((total, field, index) => (total + (isCompressedTimestamp && index === 0 && field.fDefNo === 253 ? 0 : field.size)), 0);
|
|
535
552
|
const developerSize = developerFieldDefs.reduce((total, field) => total + field.size, 0);
|
|
536
553
|
if (startIndex + 1 + nativeSize + developerSize > dataEnd) {
|
package/dist/cjs/binary.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface MessageTypeDefinition {
|
|
|
8
8
|
fieldDefs: FieldDefinition[];
|
|
9
9
|
developerFieldDefs?: DeveloperFieldDefinition[];
|
|
10
10
|
rawData?: any[];
|
|
11
|
+
completeDefinition?: boolean;
|
|
12
|
+
reservedHeaderBit?: boolean;
|
|
11
13
|
}
|
|
12
14
|
export interface DeveloperFieldDefinition {
|
|
13
15
|
developerDataIndex: number;
|
package/dist/cjs/binary.js
CHANGED
|
@@ -423,6 +423,8 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
423
423
|
const recordHeader = blob[startIndex];
|
|
424
424
|
let localMessageType = recordHeader & 15;
|
|
425
425
|
const isCompressedTimestamp = (recordHeader & CompressedHeaderMask) === CompressedHeaderMask;
|
|
426
|
+
const reservedHeaderBit = !isCompressedTimestamp && (recordHeader & 0x10) !== 0;
|
|
427
|
+
const strictHeaders = options.strict || retainsRawMessages(options);
|
|
426
428
|
if (isCompressedTimestamp) {
|
|
427
429
|
// compressed timestamp
|
|
428
430
|
localMessageType = (recordHeader & CompressedLocalMsgNumMask) >> 5;
|
|
@@ -430,15 +432,16 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
430
432
|
else if ((recordHeader & 64) === 64) {
|
|
431
433
|
// is definition message
|
|
432
434
|
// startIndex + 1 is reserved
|
|
433
|
-
|
|
435
|
+
const requireCompleteDefinition = strictHeaders || reservedHeaderBit;
|
|
436
|
+
if (requireCompleteDefinition && startIndex + 6 > dataEnd) {
|
|
434
437
|
throw new Error('Invalid FIT definition bounds');
|
|
435
438
|
}
|
|
436
439
|
const hasDeveloperData = (recordHeader & 32) === 32;
|
|
437
440
|
const lEnd = blob[startIndex + 2] === 0;
|
|
438
441
|
const numberOfFields = blob[startIndex + 5];
|
|
439
442
|
const nativeDefinitionsEnd = startIndex + 6 + numberOfFields * 3;
|
|
440
|
-
if (
|
|
441
|
-
&& ((
|
|
443
|
+
if (requireCompleteDefinition
|
|
444
|
+
&& ((strictHeaders && reservedHeaderBit)
|
|
442
445
|
|| blob[startIndex + 1] !== 0
|
|
443
446
|
|| blob[startIndex + 2] > 1
|
|
444
447
|
|| nativeDefinitionsEnd > dataEnd
|
|
@@ -450,7 +453,7 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
450
453
|
: 0;
|
|
451
454
|
const definitionEnd = nativeDefinitionsEnd
|
|
452
455
|
+ (hasDeveloperData ? 1 + numberOfDeveloperDataFields * 3 : 0);
|
|
453
|
-
if (
|
|
456
|
+
if (requireCompleteDefinition && definitionEnd > dataEnd) {
|
|
454
457
|
throw new Error('Invalid FIT developer definition bounds');
|
|
455
458
|
}
|
|
456
459
|
const mTypeDef = {
|
|
@@ -463,6 +466,13 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
463
466
|
fieldDefs: [],
|
|
464
467
|
developerFieldDefs: [],
|
|
465
468
|
rawData: [],
|
|
469
|
+
// A later data header may use the reserved bit with an ordinary
|
|
470
|
+
// definition. Do not extend legacy partial-record recovery to that case.
|
|
471
|
+
completeDefinition: startIndex + 6 <= dataEnd
|
|
472
|
+
&& blob[startIndex + 1] === 0
|
|
473
|
+
&& blob[startIndex + 2] <= 1
|
|
474
|
+
&& definitionEnd <= dataEnd,
|
|
475
|
+
reservedHeaderBit,
|
|
466
476
|
};
|
|
467
477
|
const message = (0, messages_js_1.getFitMessage)(mTypeDef.globalMessageNumber);
|
|
468
478
|
for (let i = 0; i < numberOfFields; i++) {
|
|
@@ -515,13 +525,20 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
515
525
|
nextIndex: hasDeveloperData ? nextIndexWithDeveloperData : nextIndex,
|
|
516
526
|
};
|
|
517
527
|
}
|
|
518
|
-
|
|
528
|
+
// Some existing producer files set bit 4 without changing the record layout.
|
|
529
|
+
// Recover only that bit in ordinary decoding; bit 5 and strict/raw parsing
|
|
530
|
+
// retain their previous rejection policy. Never rewrite the source bytes.
|
|
531
|
+
if (!isCompressedTimestamp && ((recordHeader & 0x20) !== 0 || (strictHeaders && reservedHeaderBit))) {
|
|
519
532
|
throw new Error('Invalid FIT data record header');
|
|
520
533
|
}
|
|
521
534
|
const messageType = messageTypes[localMessageType];
|
|
522
535
|
if (!messageType) {
|
|
523
536
|
throw new Error('FIT data record has no local definition');
|
|
524
537
|
}
|
|
538
|
+
const reservedHeaderCompatibility = reservedHeaderBit || messageType.reservedHeaderBit;
|
|
539
|
+
if (reservedHeaderCompatibility && !messageType.completeDefinition) {
|
|
540
|
+
throw new Error('Invalid FIT definition for reserved header compatibility');
|
|
541
|
+
}
|
|
525
542
|
let messageSize = 0;
|
|
526
543
|
let readDataFromIndex = startIndex + 1;
|
|
527
544
|
const fields = {};
|
|
@@ -536,7 +553,7 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
536
553
|
const rawDeveloperFields = includeRawDeveloperFields || includeRawMessage ? [] : undefined;
|
|
537
554
|
const unmappedFields = options.includeUnmappedMessages ? [] : undefined;
|
|
538
555
|
const unmappedDeveloperFields = options.includeUnmappedMessages ? [] : undefined;
|
|
539
|
-
if (
|
|
556
|
+
if (strictHeaders || reservedHeaderCompatibility) {
|
|
540
557
|
const nativeSize = messageType.fieldDefs.reduce((total, field, index) => (total + (isCompressedTimestamp && index === 0 && field.fDefNo === 253 ? 0 : field.size)), 0);
|
|
541
558
|
const developerSize = developerFieldDefs.reduce((total, field) => total + field.size, 0);
|
|
542
559
|
if (startIndex + 1 + nativeSize + developerSize > dataEnd) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fit-file-parser",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "6.2.
|
|
4
|
+
"version": "6.2.2",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "JavaScript and TypeScript library for parsing and encoding FIT files, with developer-field support, raw message access, and ESM/CommonJS builds.",
|
|
7
7
|
"author": {
|