Merge pull request #1101 from VG31OP/fix/tle-catalog-matching

fix(satellites): reject TLE pairs with mismatched catalog numbers (#906, #936)
This commit is contained in:
Sameh Khamis authored and GitHub committed 2026-10-17 14:00:30 -07:00
commit dd079db9b3
4 files changed
+67 -5

No files matched your search

@@ -0,0 +1,3 @@
- Skip TLE sets whose line 1 and line 2 carry different catalog numbers, so a
spliced catalog cannot pair one satellite's elements with another's; the next
valid set still parses (VG31OP, #1101).
+4 -1
View File
@@ -1474,7 +1474,10 @@ access. Callers retain validation, transport and response policy.
The shared TLE text parser skips incomplete named sets and resumes at the next The shared TLE text parser skips incomplete named sets and resumes at the next
complete set. A missing orbital line does not discard the later satellites in complete set. A missing orbital line does not discard the later satellites in
the catalog; satellite layers and next-pass tools use the same parser. the catalog. A set whose two lines carry different catalog numbers, or a
malformed catalog number, is skipped the same way, so a spliced catalog cannot
pair one satellite's elements with another's. Satellite layers and next-pass
tools use the same parser.
## Build configuration and local provider boundaries ## Build configuration and local provider boundaries
+11 -4
View File
@@ -13,7 +13,14 @@ export function parseTleText(text) {
const name = lines[i]; const name = lines[i];
const line1 = lines[i + 1]; const line1 = lines[i + 1];
const line2 = lines[i + 2]; const line2 = lines[i + 2];
if (line1.startsWith('1 ') && line2.startsWith('2 ')) { const cat1 = tleCatalogNumber(line1);
const cat2 = tleCatalogNumber(line2);
if (
line1.startsWith('1 ') &&
line2.startsWith('2 ') &&
cat1 !== null &&
cat1 === cat2
) {
result.push({ name, line1, line2 }); result.push({ name, line1, line2 });
i += 2; i += 2;
} }
@@ -60,8 +67,8 @@ export function noradIdFromSatnum(satnum) {
return decodeCatalogField(String(satnum ?? '').trim()) ?? Number.NaN; return decodeCatalogField(String(satnum ?? '').trim()) ?? Number.NaN;
} }
/** The NORAD catalog number from TLE line 1 (columns 3–7), or null. */ /** The NORAD catalog number from TLE line 1 or 2 (columns 3–7), or null. */
export function tleCatalogNumber(line1) { export function tleCatalogNumber(line) {
const field = String(line1).slice(2, 7); const field = String(line).slice(2, 7);
return decodeCatalogField(field.replace(/^ +/, '')); return decodeCatalogField(field.replace(/^ +/, ''));
} }
+49
View File
@@ -56,6 +56,55 @@ for (const missingLine of [1, 2]) {
}); });
} }
test('TLE parsing rejects pairs with mismatched catalog numbers and resumes', () => {
const text = `SAT A
1 25544U 98067A 24001.50000000 .00016717 00000-0 30270-3 0 9994
2 48274 41.4697 175.8913 0005643 312.8370 141.6893 15.61287398150000
SAT B
1 48274U 21035A 24001.50000000 .00020137 00000-0 24481-3 0 9993
2 48274 41.4697 175.8913 0005643 312.8370 141.6893 15.61287398150000
`;
const entries = parseTleText(text);
assert.deepEqual(
entries.map((entry) => entry.name),
['SAT B'],
);
assert.equal(tleCatalogNumber(entries[0].line1), 48274);
assert.equal(tleCatalogNumber(entries[0].line2), 48274);
});
test('TLE parsing accepts matching Alpha-5 pairs and rejects mismatched Alpha-5 pairs', () => {
const text = `SAT MISMATCH
1 A0000U 98067A 24001.50000000 .00016717 00000-0 30270-3 0 9994
2 A0001 41.4697 175.8913 0005643 312.8370 141.6893 15.61287398150000
SAT ALPHA5
1 A0000U 98067A 24001.50000000 .00016717 00000-0 30270-3 0 9994
2 A0000 41.4697 175.8913 0005643 312.8370 141.6893 15.61287398150000
`;
const entries = parseTleText(text);
assert.deepEqual(
entries.map((entry) => entry.name),
['SAT ALPHA5'],
);
assert.equal(tleCatalogNumber(entries[0].line1), 100000);
assert.equal(tleCatalogNumber(entries[0].line2), 100000);
});
test('TLE parsing rejects a pair whose catalog fields are malformed and resumes', () => {
// Read as digits up to the first non-digit, both fields would be 2554.
const text = `SAT MALFORMED
1 2554AU 98067A 24001.50000000 .00016717 00000-0 30270-3 0 9994
2 2554B 51.6416 247.4627 0006703 130.5360 325.0288 15.50377579432414
SAT HALF MALFORMED
1 2554AU 98067A 24001.50000000 .00016717 00000-0 30270-3 0 9994
2 02554 51.6416 247.4627 0006703 130.5360 325.0288 15.50377579432414
${TLE}`;
assert.deepEqual(
parseTleText(text).map((entry) => entry.name),
['ISS (ZARYA)', 'CSS (TIANHE)'],
);
});
test('Alpha-5 catalog numbers decode, skipping I and O', () => { test('Alpha-5 catalog numbers decode, skipping I and O', () => {
for (const [field, id] of [ for (const [field, id] of [
['A0000', 100000], ['A0000', 100000],