fit-file-parser 5.2.0 → 5.2.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/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 5.2.1
6
+
7
+ ### Fixed
8
+
9
+ - Preserve unusual vendor field definitions in lossless raw-message output so
10
+ domain consumers can validate only the messages they understand.
11
+ - Add corpus checks for raw-message-only and combined raw/decoded parsing.
12
+
13
+ ## 5.2.0
14
+
5
15
  ### Added
6
16
 
7
17
  - Add opt-in, message-filterable `raw_messages` output with exact native and
package/README.md CHANGED
@@ -388,12 +388,16 @@ aggregate-only validation command:
388
388
  ```sh
389
389
  git clone https://github.com/ThomasKuehne/FIT-test-files.git ../FIT-test-files
390
390
  npm run corpus:check -- ../FIT-test-files --allow-force-recovery
391
+ npm run corpus:check -- ../FIT-test-files --allow-force-recovery --raw-messages
392
+ npm run corpus:check -- ../FIT-test-files --allow-force-recovery --raw-messages-with-decoded-output
391
393
  ```
392
394
 
393
395
  The command accepts any corpus path; the sibling location is only a convenient
394
- convention. The corpus contains a known header-CRC failure that is expected to
395
- recover only in force mode. It reports aggregate counts and never prints file
396
- names or parsed activity data.
396
+ convention. Add `--raw-messages` to exercise lossless raw-message-only parsing
397
+ for every message in each file, or `--raw-messages-with-decoded-output` to also
398
+ verify the ordinary decoded output path. The corpus contains a known header-CRC
399
+ failure that is expected to recover only in force mode. It reports aggregate
400
+ counts and never prints file names or parsed activity data.
397
401
 
398
402
  Do not edit `src/garmin_profile.generated.ts` or `src/fit_types.ts` manually.
399
403
  Update the pinned SDK, audited vendor extensions, or a generator, then run
package/dist/binary.js CHANGED
@@ -8,36 +8,14 @@ const GarminTimeOffset = 631065600000;
8
8
  const InvalidFieldData = Symbol('invalid FIT field data');
9
9
  const formatTypeMetadata = new Map();
10
10
  const uint8CompatibleTypes = new Set(['enum', 'uint8', 'byte']);
11
- const fitBaseTypeWidths = new Map([
12
- [0, 1],
13
- [1, 1],
14
- [2, 1],
15
- [3, 2],
16
- [4, 2],
17
- [5, 4],
18
- [6, 4],
19
- [7, 1],
20
- [8, 4],
21
- [9, 8],
22
- [10, 1],
23
- [11, 2],
24
- [12, 4],
25
- [13, 1],
26
- [14, 8],
27
- [15, 8],
28
- [16, 8],
29
- ]);
30
11
  function retainsRawMessages(options) {
31
12
  return options.includeRawMessages === true
32
13
  || Array.isArray(options.includeRawMessages);
33
14
  }
34
- function isValidRawFieldDefinition(size, baseType) {
35
- if (size <= 0 || (baseType & 0x60) !== 0) {
36
- return false;
37
- }
38
- const typeId = baseType & 0x1F;
39
- const width = fitBaseTypeWidths.get(typeId);
40
- return width !== undefined && (typeId === 7 || size % width === 0);
15
+ function retainsRawMessage(options, globalMessageNumber) {
16
+ return options.includeRawMessages === true
17
+ || (Array.isArray(options.includeRawMessages)
18
+ && options.includeRawMessages.includes(globalMessageNumber));
41
19
  }
42
20
  function baseTypeSize(type) {
43
21
  switch (type) {
@@ -480,18 +458,11 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
480
458
  rawData: [],
481
459
  };
482
460
  const message = getFitMessage(mTypeDef.globalMessageNumber);
483
- const nativeFieldNumbers = new Set();
484
461
  for (let i = 0; i < numberOfFields; i++) {
485
462
  const fDefIndex = startIndex + 6 + i * 3;
486
463
  const baseType = blob[fDefIndex + 2];
487
464
  const fieldNumber = blob[fDefIndex];
488
465
  const fieldSize = blob[fDefIndex + 1];
489
- if (retainsRawMessages(options)
490
- && (nativeFieldNumbers.has(fieldNumber)
491
- || !isValidRawFieldDefinition(fieldSize, baseType))) {
492
- throw new Error('Invalid FIT native field definition');
493
- }
494
- nativeFieldNumbers.add(fieldNumber);
495
466
  const wireType = FIT.types.fit_base_type[baseType];
496
467
  const { field, type, baseType: profileBaseType, array, scale, offset, units, } = message.getAttributes(blob[fDefIndex]);
497
468
  const profileCompatible = areProfileBaseTypesCompatible(profileBaseType, wireType);
@@ -515,15 +486,8 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
515
486
  };
516
487
  mTypeDef.fieldDefs.push(fDef);
517
488
  }
518
- const developerFieldNumbers = new Set();
519
489
  for (let i = 0; i < numberOfDeveloperDataFields; i++) {
520
490
  const fDefIndex = startIndex + 6 + numberOfFields * 3 + 1 + i * 3;
521
- const developerFieldKey = `${blob[fDefIndex + 2]}:${blob[fDefIndex]}`;
522
- if (retainsRawMessages(options)
523
- && (blob[fDefIndex + 1] === 0 || developerFieldNumbers.has(developerFieldKey))) {
524
- throw new Error('Invalid FIT developer field definition');
525
- }
526
- developerFieldNumbers.add(developerFieldKey);
527
491
  (_a = mTypeDef.developerFieldDefs) === null || _a === void 0 ? void 0 : _a.push({
528
492
  fieldDefinitionNumber: blob[fDefIndex],
529
493
  size: blob[fDefIndex + 1],
@@ -546,24 +510,13 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
546
510
  if (!messageType) {
547
511
  throw new Error('FIT data record has no local definition');
548
512
  }
549
- if (isCompressedTimestamp && retainsRawMessages(options)) {
550
- const timestampField = messageType.fieldDefs[0];
551
- if (!timestampField
552
- || timestampField.fDefNo !== 253
553
- || timestampField.size !== 4
554
- || (timestampField.baseTypeNo & 0x1F) !== 6) {
555
- throw new Error('Invalid FIT compressed timestamp definition');
556
- }
557
- }
558
513
  let messageSize = 0;
559
514
  let readDataFromIndex = startIndex + 1;
560
515
  const fields = {};
561
516
  const message = getFitMessage(messageType.globalMessageNumber);
562
517
  const developerFieldDefs = (_b = messageType.developerFieldDefs) !== null && _b !== void 0 ? _b : [];
563
518
  const totalFieldCount = messageType.fieldDefs.length + developerFieldDefs.length;
564
- const includeRawMessage = options.includeRawMessages === true
565
- || (Array.isArray(options.includeRawMessages)
566
- && options.includeRawMessages.includes(messageType.globalMessageNumber));
519
+ const includeRawMessage = retainsRawMessage(options, messageType.globalMessageNumber);
567
520
  const includeRawDeveloperFields = options.includeRawDeveloperFields === true
568
521
  || (Array.isArray(options.includeRawDeveloperFields)
569
522
  && options.includeRawDeveloperFields.includes(messageType.globalMessageNumber));
@@ -14,36 +14,14 @@ const GarminTimeOffset = 631065600000;
14
14
  const InvalidFieldData = Symbol('invalid FIT field data');
15
15
  const formatTypeMetadata = new Map();
16
16
  const uint8CompatibleTypes = new Set(['enum', 'uint8', 'byte']);
17
- const fitBaseTypeWidths = new Map([
18
- [0, 1],
19
- [1, 1],
20
- [2, 1],
21
- [3, 2],
22
- [4, 2],
23
- [5, 4],
24
- [6, 4],
25
- [7, 1],
26
- [8, 4],
27
- [9, 8],
28
- [10, 1],
29
- [11, 2],
30
- [12, 4],
31
- [13, 1],
32
- [14, 8],
33
- [15, 8],
34
- [16, 8],
35
- ]);
36
17
  function retainsRawMessages(options) {
37
18
  return options.includeRawMessages === true
38
19
  || Array.isArray(options.includeRawMessages);
39
20
  }
40
- function isValidRawFieldDefinition(size, baseType) {
41
- if (size <= 0 || (baseType & 0x60) !== 0) {
42
- return false;
43
- }
44
- const typeId = baseType & 0x1F;
45
- const width = fitBaseTypeWidths.get(typeId);
46
- return width !== undefined && (typeId === 7 || size % width === 0);
21
+ function retainsRawMessage(options, globalMessageNumber) {
22
+ return options.includeRawMessages === true
23
+ || (Array.isArray(options.includeRawMessages)
24
+ && options.includeRawMessages.includes(globalMessageNumber));
47
25
  }
48
26
  function baseTypeSize(type) {
49
27
  switch (type) {
@@ -486,18 +464,11 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
486
464
  rawData: [],
487
465
  };
488
466
  const message = (0, messages_js_1.getFitMessage)(mTypeDef.globalMessageNumber);
489
- const nativeFieldNumbers = new Set();
490
467
  for (let i = 0; i < numberOfFields; i++) {
491
468
  const fDefIndex = startIndex + 6 + i * 3;
492
469
  const baseType = blob[fDefIndex + 2];
493
470
  const fieldNumber = blob[fDefIndex];
494
471
  const fieldSize = blob[fDefIndex + 1];
495
- if (retainsRawMessages(options)
496
- && (nativeFieldNumbers.has(fieldNumber)
497
- || !isValidRawFieldDefinition(fieldSize, baseType))) {
498
- throw new Error('Invalid FIT native field definition');
499
- }
500
- nativeFieldNumbers.add(fieldNumber);
501
472
  const wireType = fit_js_1.FIT.types.fit_base_type[baseType];
502
473
  const { field, type, baseType: profileBaseType, array, scale, offset, units, } = message.getAttributes(blob[fDefIndex]);
503
474
  const profileCompatible = areProfileBaseTypesCompatible(profileBaseType, wireType);
@@ -521,15 +492,8 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
521
492
  };
522
493
  mTypeDef.fieldDefs.push(fDef);
523
494
  }
524
- const developerFieldNumbers = new Set();
525
495
  for (let i = 0; i < numberOfDeveloperDataFields; i++) {
526
496
  const fDefIndex = startIndex + 6 + numberOfFields * 3 + 1 + i * 3;
527
- const developerFieldKey = `${blob[fDefIndex + 2]}:${blob[fDefIndex]}`;
528
- if (retainsRawMessages(options)
529
- && (blob[fDefIndex + 1] === 0 || developerFieldNumbers.has(developerFieldKey))) {
530
- throw new Error('Invalid FIT developer field definition');
531
- }
532
- developerFieldNumbers.add(developerFieldKey);
533
497
  (_a = mTypeDef.developerFieldDefs) === null || _a === void 0 ? void 0 : _a.push({
534
498
  fieldDefinitionNumber: blob[fDefIndex],
535
499
  size: blob[fDefIndex + 1],
@@ -552,24 +516,13 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
552
516
  if (!messageType) {
553
517
  throw new Error('FIT data record has no local definition');
554
518
  }
555
- if (isCompressedTimestamp && retainsRawMessages(options)) {
556
- const timestampField = messageType.fieldDefs[0];
557
- if (!timestampField
558
- || timestampField.fDefNo !== 253
559
- || timestampField.size !== 4
560
- || (timestampField.baseTypeNo & 0x1F) !== 6) {
561
- throw new Error('Invalid FIT compressed timestamp definition');
562
- }
563
- }
564
519
  let messageSize = 0;
565
520
  let readDataFromIndex = startIndex + 1;
566
521
  const fields = {};
567
522
  const message = (0, messages_js_1.getFitMessage)(messageType.globalMessageNumber);
568
523
  const developerFieldDefs = (_b = messageType.developerFieldDefs) !== null && _b !== void 0 ? _b : [];
569
524
  const totalFieldCount = messageType.fieldDefs.length + developerFieldDefs.length;
570
- const includeRawMessage = options.includeRawMessages === true
571
- || (Array.isArray(options.includeRawMessages)
572
- && options.includeRawMessages.includes(messageType.globalMessageNumber));
525
+ const includeRawMessage = retainsRawMessage(options, messageType.globalMessageNumber);
573
526
  const includeRawDeveloperFields = options.includeRawDeveloperFields === true
574
527
  || (Array.isArray(options.includeRawDeveloperFields)
575
528
  && options.includeRawDeveloperFields.includes(messageType.globalMessageNumber));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fit-file-parser",
3
3
  "type": "module",
4
- "version": "5.2.0",
4
+ "version": "5.2.1",
5
5
  "private": false,
6
6
  "description": "Parse your .FIT files easily, directly from JS (Garmin, Polar, Suunto)",
7
7
  "author": {