Files
gods-eye-view/src/share/viewCodec.test.mjs
T

1340 lines
43 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
LAYER_STATE_REGISTRY,
LAYER_STATE_TOKEN_ALPHABET,
LAYER_STATE_TOKEN_RESERVATIONS,
REGISTERED_LAYER_IDS,
createDefaultLayerState,
optionSpecs,
} from '../data/layerState.js';
import { VIEW_FOLLOW_KINDS, VIEW_MAPS, VIEW_STYLES } from '../view/index.js';
import { HUD_LAYOUTS } from '../hudLayoutPolicy.js';
import { ALLOCATION_STRATEGIES } from '../data/detectionPolicy.js';
import { BLOOM_INTENSITY_DEFAULT, BLOOM_SCALE_VERSION } from '../bloom.js';
import {
pick,
randomLayerState,
rng,
} from '../testSupport/shareViewFixtures.mjs';
import {
SCOPE_TERMINUS_MAX_PCT,
SCOPE_TERMINUS_MIN_PCT,
} from '../scopeMask.js';
import {
decodePanelStateParams,
decodeStyleParamState,
encodeStyleParamState,
} from '../sharelink.js';
import {
WIRE_FIXED32,
WIRE_FIXED64,
WireWriter,
crc8,
decodeBase64Url,
encodeBase64Url,
} from './wire.js';
import {
MAX_SHARE_VIEW_CHARS,
SHARE_VIEW_ENTITY_KINDS,
SHARE_VIEW_ENUMS,
SHARE_VIEW_EPOCH_S,
SHARE_VIEW_FIELDS,
BY_STYLE,
SHARE_VIEW_FLAGS,
SHARE_VIEW_FOLLOW_OFFSET_DEFAULT,
SHARE_VIEW_STYLE_DEFAULTS,
SHARE_VIEW_LAYER_OPTION_ABSENT,
SHARE_VIEW_PANELS,
SHARE_VIEW_STYLE_PARAMS,
createDefaultShareViewState,
decodeShareView,
encodeShareView,
followCameraFor,
followOffsetFromEnu,
followOffsetToEnu,
layerTokenFromNumber,
layerTokenNumber,
normalizeShareViewState,
} from './viewCodec.js';
const NOW_MS = Date.UTC(2026, 9, 26, 12, 0, 0);
const decode = (hash) => decodeShareView(hash, { nowMs: NOW_MS });
/** The payload bytes of a code, without its check byte. */
const payloadBytes = (code) => decodeBase64Url(code).slice(0, -1);
/** The code for payload bytes: base64url of the bytes and their CRC-8. */
const hashOf = (bytes) => encodeBase64Url([...bytes, crc8(bytes)]);
/** Decode and require success, returning the result. */
function decodeOk(hash) {
const result = decode(hash);
assert.equal(result.ok, true, `decode failed: ${result.reason}`);
return result;
}
function assertRoundTrip(state) {
const hash = encodeShareView(state);
const result = decodeOk(hash);
assert.deepEqual(result.state, normalizeShareViewState(state));
assert.equal(result.unknownFields, undefined);
assert.equal(result.fallback, undefined);
assert.equal(encodeShareView(result.state), hash, 'encoding is canonical');
return hash;
}
function randomState(random) {
const int = (min, max) => min + Math.floor(random() * (max - min + 1));
const maybe = (p, value) => (random() < p ? value : undefined);
const style = pick(random, VIEW_STYLES);
const params = SHARE_VIEW_STYLE_PARAMS[style];
const lat = int(-900000, 900000) / 1e4;
const lon = int(-1800000, 1800000) / 1e4;
const state = {
lat,
lon,
alt: pick(random, [800, int(-400, 50000), int(0, 40_000_000)]),
heading: int(0, 359),
pitch: int(-90, 90),
roll: pick(random, [0, int(0, 359)]),
style,
styleParams:
params && random() < 0.6
? Object.fromEntries(
params
.filter(() => random() < 0.7)
.map((s) => [s.key, int(s.min * 100, s.max * 100) / 100]),
)
: null,
bloomIntensity: maybe(0.5, int(0, 100)),
bloomVersion: maybe(0.3, pick(random, [1, 2])),
sharpenIntensity: maybe(0.5, int(0, 100)),
hudVariant: maybe(0.5, pick(random, [...HUD_LAYOUTS, 'future-hud'])),
detectionMode: maybe(0.6, pick(random, SHARE_VIEW_ENUMS.detectionMode)),
detectionDensity: maybe(0.6, pick(random, [0, 25, 50, 75, 100])),
detectionAllocation: maybe(0.4, pick(random, ALLOCATION_STRATEGIES)),
detectionFadePct: maybe(0.5, int(0, 40)),
detectionOutsideOpacityPct: maybe(0.5, int(0, 100)),
scopeFeatherPct: maybe(0.5, int(0, 200)),
scopeTerminusPct: maybe(
0.3,
int(SCOPE_TERMINUS_MIN_PCT, SCOPE_TERMINUS_MAX_PCT),
),
mapStack: maybe(0.5, pick(random, [...VIEW_MAPS, 'future-map'])),
layerState: random() < 0.7 ? randomLayerState(random) : null,
panelState:
random() < 0.6
? {
specs: SHARE_VIEW_PANELS.filter(() => random() < 0.8).map(
(panel) => ({
id: panel.id,
collapsed: random() < 0.5,
pinned: panel.pinnable
? pick(random, [true, false, null])
: null,
}),
),
}
: null,
sharedAtMs: maybe(0.7, int(1, 2_100_000_000) * 1000),
};
for (const flag of SHARE_VIEW_FLAGS)
if (random() < 0.5) state[flag.key] = random() < 0.5;
if (random() < 0.4) {
const entity = pick(random, SHARE_VIEW_ENTITY_KINDS);
const id =
entity.kind === 'satellite'
? String(int(1, 99999))
: entity.kind === 'vessel'
? pick(random, [
String(int(100000000, 999999999)),
'002442000',
'tmp-7',
])
: pick(random, [
int(0, 0xffffff).toString(16).padStart(6, '0'),
'~abc123',
'adsb_x-1',
]);
state.view = {
kind: 'thing',
entity: { kind: entity.kind, id },
offset:
random() < 0.6
? { range: int(0, 200000), heading: int(0, 359), pitch: int(-90, 90) }
: null,
lastKnown:
random() < 0.6
? {
lat: Math.max(-90, Math.min(90, lat + int(-5000, 5000) / 1e4)),
lon: Math.max(-180, Math.min(180, lon + int(-5000, 5000) / 1e4)),
alt: int(-100, 500000),
observedAtMs: maybe(0.8, int(1, 2_100_000_000) * 1000),
}
: null,
};
}
return state;
}
test('an all-default view is the version byte and its check byte', () => {
const hash = encodeShareView(createDefaultShareViewState());
assert.equal(hash, 'AQc');
assert.deepEqual(decodeOk(hash).state, normalizeShareViewState({}));
assert.deepEqual(normalizeShareViewState({}), createDefaultShareViewState());
});
test('each setting costs bytes only when it differs from its default', () => {
const base = payloadBytes(encodeShareView({})).length;
const changes = {
lat: 10,
lon: 10,
alt: 900,
heading: 1,
pitch: -36,
roll: 1,
sharedAtMs: NOW_MS,
style: 'noir',
mapStack: 'osm',
hudVariant: 'cyber',
bloomIntensity: 1,
bloomVersion: 1,
sharpenIntensity: 50,
detectionMode: 'SPARSE',
detectionDensity: 100,
detectionAllocation: 'WEIGHTED',
detectionFadePct: 8,
detectionOutsideOpacityPct: 2,
scopeFeatherPct: 99,
scopeTerminusPct: 95,
};
const defaults = createDefaultShareViewState();
for (const [key, value] of Object.entries(changes)) {
assert.equal(
payloadBytes(encodeShareView({ [key]: defaults[key] })).length,
base,
`${key} at its default is omitted`,
);
const changed = { [key]: value };
if (key === 'detectionMode') changed.detectionDensity = 75;
assert.ok(
payloadBytes(assertRoundTrip(changed)).length > base,
`${key} is encoded`,
);
}
for (const flag of SHARE_VIEW_FLAGS) {
assert.equal(
payloadBytes(encodeShareView({ [flag.key]: defaults[flag.key] })).length,
base,
);
assert.equal(
payloadBytes(assertRoundTrip({ [flag.key]: !defaults[flag.key] })).length,
base + 2,
);
}
// All five booleans flipped still cost one field.
const flipped = Object.fromEntries(
SHARE_VIEW_FLAGS.map((f) => [f.key, !defaults[f.key]]),
);
assert.equal(payloadBytes(assertRoundTrip(flipped)).length, base + 2);
});
test('a view just switched to any style costs its camera and style only', () => {
const camera = { lat: 48.8584, lon: 2.2945, alt: 1200, heading: 120 };
const cameraOnly = payloadBytes(encodeShareView(camera)).length;
for (const style of VIEW_STYLES) {
// What the app holds right after selecting the style in a fresh session.
const fresh = { ...camera, ...SHARE_VIEW_STYLE_DEFAULTS[style], style };
const bytes = payloadBytes(assertRoundTrip(fresh)).length;
assert.equal(bytes, cameraOnly + (style === 'normal' ? 0 : 2), style);
// Changes on top of the style's look still travel and round-trip.
const params = SHARE_VIEW_STYLE_PARAMS[style];
const changed = {
...fresh,
sharpen: false,
detectionMode: 'OFF',
detectionFadePct: 16,
...(params
? {
styleParams: {
...fresh.styleParams,
[params[0].key]: params[0].min,
},
}
: {}),
};
assert.ok(payloadBytes(assertRoundTrip(changed)).length > bytes, style);
}
});
test('invalid style parameters keep the style’s own value', () => {
for (const bad of [undefined, NaN, 'x', null])
assertRoundTrip({ style: 'noir', styleParams: { contrastAmt: bad } });
assert.equal(
normalizeShareViewState({ style: 'noir', styleParams: { grainAmt: NaN } })
.styleParams.grainAmt,
0.5,
);
});
test('every registered field round-trips at once', () => {
const layerState = createDefaultLayerState();
layerState.enabledLayerIds = [
'flights',
'satellites',
'radio',
'recent-imagery',
'street-level',
];
Object.assign(layerState.options.flights, {
models3d: true,
models3dMode: 'all',
});
Object.assign(layerState.options.radio, {
filter: 'genre:jazz & blues',
volume: 0.35,
});
Object.assign(layerState.options['recent-imagery'], {
west: -12345678,
east: 12345678,
south: -8505110,
north: 8505110,
a: 'S30:2026-09-18',
b: 'VIIRS:2025-01-02',
mode: 2,
split: 37,
viirs: true,
});
Object.assign(layerState.options['street-level'], {
mapillary: false,
providerSwitches: 'abc-1*def-0',
sinceDays: 365,
});
const state = {
view: {
kind: 'thing',
entity: { kind: 'aircraft', id: 'a1b2c3' },
offset: { range: 1500, heading: 300, pitch: -20 },
lastKnown: {
lat: -33.8601,
lon: 151.2101,
alt: 10668,
observedAtMs: NOW_MS - 12_000,
},
},
lat: -33.8568,
lon: 151.2153,
alt: 12000,
heading: 270,
pitch: -12,
roll: 2,
style: 'thermal',
styleParams: {
sensitivity: 0.42,
bloom: 1,
mode: 0,
pixelation: 3.5,
palette: 0.25,
},
bloom: true,
sharpen: false,
bloomIntensity: 84,
bloomVersion: 1,
sharpenIntensity: 65,
hudVariant: 'operator',
hudVisible: false,
detectionMode: 'SPARSE',
detectionDensity: 100,
detectionAllocation: 'WEIGHTED',
detectionFadePct: 16,
detectionOutsideOpacityPct: 5,
celestialRing: true,
scopeEnabled: false,
scopeFeatherPct: 35,
scopeTerminusPct: 97,
mapStack: 'esri-imagery',
layerState,
panelState: {
specs: SHARE_VIEW_PANELS.map((panel) => ({
id: panel.id,
collapsed: !panel.collapsed,
pinned: panel.pinnable ? true : null,
})),
},
sharedAtMs: NOW_MS,
};
// A thing view derives its camera (roll 0), so the camera fields are
// checked on the same state as a place view.
const place = { ...state };
delete place.view;
for (const subject of [state, place]) {
const normalized = normalizeShareViewState(subject);
for (const key of Object.keys(subject)) {
if (subject === state && key === 'roll') continue;
assert.notDeepEqual(
normalized[key],
createDefaultShareViewState()[key] ?? undefined,
key,
);
}
assertRoundTrip(subject);
}
});
test('generated states round-trip exactly and encode canonically', () => {
const random = rng(0x5eed);
let things = 0;
for (let i = 0; i < 1500; i += 1) {
const state = randomState(random);
if (state.view?.kind === 'thing') things += 1;
const hash = encodeShareView(state);
const result = decodeShareView(hash, { nowMs: Number.MAX_SAFE_INTEGER });
assert.equal(result.ok, true, `case ${i}: ${result.reason}`);
assert.deepEqual(result.state, normalizeShareViewState(state), `case ${i}`);
assert.equal(encodeShareView(result.state), hash, `case ${i} canonical`);
assert.ok(hash.length < MAX_SHARE_VIEW_CHARS, `case ${i} size`);
}
assert.ok(things > 400, 'generator covers thing views');
});
test('normalization is idempotent', () => {
const random = rng(42);
for (let i = 0; i < 300; i += 1) {
const once = normalizeShareViewState(randomState(random));
assert.deepEqual(normalizeShareViewState(once), once);
}
});
test('precision: coordinates 1e-4 degrees, angles and lengths whole units', () => {
const { state } = decodeOk(
encodeShareView({
lat: 30.26724,
lon: -97.74316,
alt: 600.6,
heading: 15.4,
pitch: -30.5,
roll: 359.6,
}),
);
assert.equal(state.lat, 30.2672);
assert.equal(state.lon, -97.7432);
assert.equal(state.alt, 601);
assert.equal(state.heading, 15);
assert.equal(state.pitch, -30);
assert.equal(state.roll, 0, '359.6 rounds to 360, which is 0');
// Angles wrap into [0, 360) and are stored as the nearer signed turn.
assert.equal(decodeOk(encodeShareView({ heading: -10 })).state.heading, 350);
assert.equal(payloadBytes(encodeShareView({ heading: 350 })).length, 3);
// Out-of-range input is clamped or wrapped, never rejected.
const wild = decodeOk(
encodeShareView({ lat: 95, lon: 190, pitch: -120 }),
).state;
assert.deepEqual([wild.lat, wild.lon, wild.pitch], [90, -170, -90]);
});
test('defaults travel as frozen meanings that match today’s app defaults', () => {
const defaults = createDefaultShareViewState();
// When an app default moves, these tests should be updated to document the
// drift; the codec's defaults table must NOT change (see SHARE-LINK-FORMAT).
assert.equal(defaults.bloomIntensity, BLOOM_INTENSITY_DEFAULT);
assert.equal(defaults.bloomVersion, BLOOM_SCALE_VERSION);
assert.equal(defaults.hudVariant, HUD_LAYOUTS[0]);
// Codes are append-only: the retired Operator layout keeps code 1 so old
// links still parse, and the app restores them as Tactical.
assert.deepEqual(SHARE_VIEW_ENUMS.hudVariant, [
'tactical',
'operator',
'minimal',
'cyber',
]);
assert.deepEqual(
SHARE_VIEW_ENUMS.hudVariant.filter((variant) => variant !== 'operator'),
[...HUD_LAYOUTS],
);
assert.deepEqual(SHARE_VIEW_ENUMS.style, [...VIEW_STYLES]);
assert.deepEqual(SHARE_VIEW_ENUMS.mapStack, [...VIEW_MAPS]);
assert.deepEqual(SHARE_VIEW_ENUMS.detectionAllocation, [
...ALLOCATION_STRATEGIES,
]);
for (const kind of VIEW_FOLLOW_KINDS)
assert.ok(
SHARE_VIEW_ENTITY_KINDS.some((entry) => entry.kind === kind),
kind,
);
});
test('the field registry is well formed and permanent', () => {
const numbers = SHARE_VIEW_FIELDS.map((f) => f.number);
assert.equal(new Set(numbers).size, numbers.length);
assert.equal(
new Set(SHARE_VIEW_FIELDS.map((f) => f.key)).size,
numbers.length,
);
// Snapshot: changing a number, key or default breaks links in the wild.
assert.deepEqual(
SHARE_VIEW_FIELDS.map((f) => [f.number, f.key, f.default]),
[
[1, 'view', 'place'],
[2, 'lat', 0],
[3, 'lon', 0],
[4, 'alt', 800],
[5, 'heading', 0],
[6, 'pitch', -35],
[7, 'roll', 0],
[8, 'sharedAtMs', null],
[9, 'flags', 0],
[10, 'layerState', null],
[11, 'layerOptions', null],
[12, 'panelState', null],
[13, 'thing', null],
[14, 'style', 'normal'],
[15, 'mapStack', 'photoreal'],
[16, 'hudVariant', BY_STYLE],
[17, 'bloomIntensity', BY_STYLE],
[18, 'bloomVersion', 2],
[19, 'sharpenIntensity', BY_STYLE],
[20, 'detectionMode', BY_STYLE],
[21, 'detectionDensity', BY_STYLE],
[22, 'detectionAllocation', BY_STYLE],
[23, 'detectionFadePct', BY_STYLE],
[24, 'detectionOutsideOpacityPct', BY_STYLE],
[25, 'scopeFeatherPct', 100],
[26, 'scopeTerminusPct', null],
[27, 'styleParams', BY_STYLE],
],
);
assert.deepEqual(
SHARE_VIEW_FLAGS.map((f) => [f.bit, f.key, f.default]),
[
[0, 'bloom', BY_STYLE],
[1, 'sharpen', BY_STYLE],
[2, 'hudVisible', BY_STYLE],
[3, 'celestialRing', BY_STYLE],
[4, 'scopeEnabled', true],
],
);
// The style-applied defaults are frozen meanings too. If the app's style
// tables move and this fails, freeze these values in the codec instead of
// updating the snapshot: links already shared depend on them.
const common = {
bloom: false,
bloomIntensity: 0,
sharpen: true,
sharpenIntensity: 49,
hudVariant: 'tactical',
hudVisible: true,
detectionMode: 'DENSE',
detectionDensity: 75,
detectionAllocation: 'ELASTIC',
detectionFadePct: 7,
detectionOutsideOpacityPct: 1,
celestialRing: false,
};
assert.deepEqual(JSON.parse(JSON.stringify(SHARE_VIEW_STYLE_DEFAULTS)), {
normal: { ...common, styleParams: null },
retro: {
...common,
styleParams: { pixelation: 1, distortion: 0, instability: 0.42 },
},
surveillance: {
...common,
styleParams: {
gain: 0.18,
bloom: 0.22,
scanlineStr: 0.96,
pixelation: 1,
},
},
thermal: {
...common,
styleParams: {
sensitivity: 0.85,
bloom: 0.2,
mode: 0.33,
pixelation: 1,
palette: 0,
},
},
anime: { ...common, styleParams: { saturation: 1, edgeThick: 0.5 } },
noir: {
...common,
styleParams: { contrastAmt: 1.2, grainAmt: 0.5, vignetteAmt: 0.5 },
},
});
assert.deepEqual(
SHARE_VIEW_ENTITY_KINDS.map((e) => [e.code, e.kind]),
[
[1, 'aircraft'],
[2, 'military_aircraft'],
[3, 'vessel'],
[4, 'satellite'],
],
);
assert.deepEqual(
SHARE_VIEW_PANELS.map((p) => [p.number, p.id]),
SHARE_VIEW_PANELS.map((p, i) => [i, p.id]),
);
});
test('the panel table agrees with the long form’s panel tokens', () => {
for (const panel of SHARE_VIEW_PANELS) {
const decoded = decodePanelStateParams(
new URLSearchParams(`v=2&ui=${panel.token}.c.1_${panel.token}.p.1`),
);
assert.deepEqual(decoded.specs, [
{ id: panel.id, collapsed: true, pinned: panel.pinnable ? true : null },
]);
}
});
test('the style parameter table agrees with the long form’s `sp` codec', () => {
for (const [style, specs] of Object.entries(SHARE_VIEW_STYLE_PARAMS)) {
const values = Object.fromEntries(
specs.map((s, i) => [s.key, s.min + ((s.max - s.min) * (i + 1)) / 7]),
);
const params = new URLSearchParams('v=2');
encodeStyleParamState(params, style, values);
const legacy = decodeStyleParamState(params, style);
assert.deepEqual(
normalizeShareViewState({ style, styleParams: values }).styleParams,
legacy,
style,
);
assert.deepEqual(
decodeOk(encodeShareView({ style, styleParams: values })).state
.styleParams,
legacy,
style,
);
}
});
test('every layer option has a frozen absent meaning', () => {
for (const entry of LAYER_STATE_REGISTRY) {
if (!entry.optionOwner) continue;
for (const spec of optionSpecs(entry.optionOwner)) {
assert.ok(
Object.hasOwn(
SHARE_VIEW_LAYER_OPTION_ABSENT[entry.optionOwner] || {},
spec.key,
),
`${entry.optionOwner}.${spec.key} needs an entry in SHARE_VIEW_LAYER_OPTION_ABSENT`,
);
}
}
// A default-only layer state writes no option bytes at all.
const layerState = createDefaultLayerState();
layerState.enabledLayerIds = [...REGISTERED_LAYER_IDS];
const bytes = payloadBytes(encodeShareView({ layerState }));
assert.equal(bytes.includes(0x5a), false, 'no layer-options field');
});
test('layer tokens map to permanent numbers covering the whole ledger', () => {
for (const token of Object.values(LAYER_STATE_TOKEN_RESERVATIONS)) {
assert.equal(layerTokenFromNumber(layerTokenNumber(token)), token);
}
for (const token of ['0', '9', 'a', 'z', '00', '0z', 'zz'])
assert.equal(layerTokenFromNumber(layerTokenNumber(token)), token);
assert.equal(layerTokenNumber('00'), 36);
assert.equal(layerTokenFromNumber(36 + 36 * 36), null);
const numbers = LAYER_STATE_REGISTRY.map((e) => layerTokenNumber(e.token));
assert.equal(new Set(numbers).size, numbers.length);
});
test('layer sets: absent, explicitly empty and populated stay distinct', () => {
assert.equal(
decodeOk(encodeShareView({ layerState: null })).state.layerState,
null,
);
const empty = decodeOk(
encodeShareView({ layerState: createDefaultLayerState() }),
).state.layerState;
assert.deepEqual(empty, createDefaultLayerState());
const all = createDefaultLayerState();
all.enabledLayerIds = [...REGISTERED_LAYER_IDS];
assertRoundTrip({ layerState: all });
});
test('panels: only differences from the frozen layout are written', () => {
const defaults = {
specs: SHARE_VIEW_PANELS.map((p) => ({
id: p.id,
collapsed: p.collapsed,
pinned: p.pinnable ? false : null,
})),
};
const base = payloadBytes(encodeShareView({})).length;
assert.equal(
payloadBytes(assertRoundTrip({ panelState: defaults })).length,
base + 2,
);
const oneChanged = structuredClone(defaults);
oneChanged.specs[3].collapsed = false;
assert.equal(
payloadBytes(assertRoundTrip({ panelState: oneChanged })).length,
base + 3,
);
// A partial panel list (an older link) keeps its gaps as "no opinion".
const partial = {
specs: [{ id: 'cctv-panel', collapsed: false, pinned: null }],
};
assert.deepEqual(
decodeOk(encodeShareView({ panelState: partial })).state.panelState,
partial,
);
});
test('a place view opens at its camera; a thing view also names what to follow', () => {
const place = decodeOk(encodeShareView({ lat: 1, lon: 2 })).state;
assert.deepEqual(place.view, { kind: 'place' });
for (const entry of SHARE_VIEW_ENTITY_KINDS) {
const id = entry.idForm === 'hex24' ? '3c6444' : '25544';
const { state } = decodeOk(
encodeShareView({
lat: 1,
lon: 2,
view: { kind: 'thing', entity: { kind: entry.kind, id } },
}),
);
assert.deepEqual(state.view, {
kind: 'thing',
entity: { kind: entry.kind, id },
offset: { ...SHARE_VIEW_FOLLOW_OFFSET_DEFAULT },
lastKnown: null,
});
// Without a last-known fix the view keeps its stored camera.
assert.equal(state.lat, 1);
assert.equal(state.lon, 2);
}
});
test('thing ids use the compact numeric form when they can, text otherwise', () => {
const size = (kind, id) =>
payloadBytes(
encodeShareView({ view: { kind: 'thing', entity: { kind, id } } }),
).length;
assert.equal(size('aircraft', 'ffffff'), size('aircraft', '000001') + 3);
assert.ok(size('aircraft', '~abc123') > size('aircraft', 'abc123'));
assert.ok(
size('vessel', '366999712') < size('vessel', '002442000'),
'leading zeros keep the text form',
);
for (const [kind, id] of [
['aircraft', '000000'],
['vessel', '002442000'],
['military_aircraft', 'adsb_x-1'],
['satellite', '1'],
])
assert.equal(
decodeOk(
encodeShareView({ view: { kind: 'thing', entity: { kind, id } } }),
).state.view.entity.id,
id,
);
// Invalid ids and kinds normalize to a place view.
for (const entity of [
{ kind: 'aircraft', id: 'NOT VALID' },
{ kind: 'satellite', id: 'abc' },
{ kind: 'ufo', id: '1' },
])
assert.deepEqual(
normalizeShareViewState({ view: { kind: 'thing', entity } }).view,
{ kind: 'place' },
);
assert.equal(
normalizeShareViewState({
view: { kind: 'thing', entity: { kind: 'aircraft', id: 'ABC123' } },
}).view.entity.id,
'abc123',
);
});
test('last-known time is stored relative to the created-at time when there is one', () => {
const lastKnown = {
lat: 10.0012,
lon: 20.0034,
alt: 11000,
observedAtMs: NOW_MS - 15_000,
};
const view = {
kind: 'thing',
entity: { kind: 'aircraft', id: 'a1b2c3' },
lastKnown,
};
const withCreated = assertRoundTrip({
lat: 10,
lon: 20,
sharedAtMs: NOW_MS,
view,
});
const withoutCreated = assertRoundTrip({ lat: 10, lon: 20, view });
assert.deepEqual(decodeOk(withoutCreated).state.view.lastKnown, lastKnown);
// Relative age (15 s) is 1 byte; the absolute time is 4.
assert.ok(
payloadBytes(withCreated).length - 5 < payloadBytes(withoutCreated).length,
);
});
test('tracking selections in layer options become a thing view', () => {
const layerState = createDefaultLayerState();
layerState.enabledLayerIds = ['military'];
layerState.options.flights.selectedMilitaryTrackingId = 'AE1234';
const state = normalizeShareViewState({ layerState });
assert.deepEqual(state.view, {
kind: 'thing',
entity: { kind: 'military_aircraft', id: 'ae1234' },
offset: { ...SHARE_VIEW_FOLLOW_OFFSET_DEFAULT },
lastKnown: null,
});
assert.equal(
state.layerState.options.flights.selectedMilitaryTrackingId,
null,
);
assertRoundTrip({ layerState });
});
test('a created-at time in the future is dropped, like the long form’s `at`', () => {
const hash = encodeShareView({ sharedAtMs: NOW_MS + 60_000 });
assert.equal(decode(hash).state.sharedAtMs, null);
assert.equal(
decodeShareView(hash, { nowMs: NOW_MS + 60_000 }).state.sharedAtMs,
NOW_MS + 60_000,
);
// Times on either side of the format epoch survive.
for (const ms of [1000, (SHARE_VIEW_EPOCH_S - 86400) * 1000, NOW_MS])
assert.equal(
decode(encodeShareView({ sharedAtMs: ms })).state.sharedAtMs,
ms,
);
});
test('a code is the bare base64url payload; URL forms are not codes', () => {
const code = encodeShareView({ lat: 51.5, lon: -0.12 });
assert.match(code, /^[A-Za-z0-9_-]+$/);
for (const form of [`#${code}`, `s=${code}`, `/share/${code}`])
assert.equal(decode(form).ok, false, form);
});
/* ---------------- forward compatibility ---------------- */
/** Append raw fields to an encoded hash, as a newer encoder would. */
function withExtraFields(hash, write) {
const writer = new WireWriter();
writer.bytes.push(...payloadBytes(hash));
write(writer);
return hashOf(writer.finish());
}
test('unknown top-level fields of every wire type are skipped and reported', () => {
const state = {
lat: 48.8584,
lon: 2.2945,
style: 'noir',
layerState: { enabledLayerIds: ['flights'] },
};
const hash = encodeShareView(state);
const future = withExtraFields(hash, (w) => {
w.uintField(40, 123456);
w.bytesField(41, [9, 9, 9, 0xff, 0]);
w.tag(42, WIRE_FIXED32).bytes.push(1, 2, 3, 4);
w.tag(43, WIRE_FIXED64).bytes.push(1, 2, 3, 4, 5, 6, 7, 8);
w.sintField(2000, -5);
});
const result = decodeOk(future);
assert.deepEqual(result.state, decodeOk(hash).state);
assert.deepEqual(
result.unknownFields.map((u) => [u.scope, u.field, u.wireType]),
[
['view', 40, 0],
['view', 41, 2],
['view', 42, 5],
['view', 43, 1],
['view', 2000, 0],
],
);
});
test('unknown fields inside a thing view, codes and bits are skipped too', () => {
const thing = new WireWriter();
thing.uintField(1, 0xa1b2c3).uintField(5, 900);
thing.textField(20, 'cockpit'); // a future thing field
const writer = new WireWriter();
writer.bytes.push(1);
writer.sintField(2, 100000).uintField(9, 0b100001); // flag bit 5 unknown
writer.bytesField(
10,
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
); // layer 160
writer.bytesField(12, new WireWriter().varint(11 * 16 + 4 + 1).finish()); // panel 11
writer.uintField(14, 99); // style code 99
writer.bytesField(13, thing.finish());
const result = decodeOk(hashOf(writer.finish()));
assert.deepEqual(result.state.view, {
kind: 'thing',
entity: { kind: 'aircraft', id: 'a1b2c3' },
offset: { ...SHARE_VIEW_FOLLOW_OFFSET_DEFAULT, range: 900 },
lastKnown: null,
});
assert.equal(result.state.bloom, true);
assert.equal(result.state.style, 'normal');
// The followed entity's layer is implied.
assert.deepEqual(result.state.layerState.enabledLayerIds, ['flights']);
assert.deepEqual(
result.unknownFields.map((u) => [u.scope, u.field ?? u.code]),
[
['enum:style', 99],
['flags', 32],
['layer', 160],
['panel', 11],
['thing', 20],
],
);
});
test('an unknown view kind falls back to the stored camera', () => {
const camera = {
lat: 35.6762,
lon: 139.6503,
alt: 2500,
heading: 45,
pitch: -20,
};
const base = payloadBytes(encodeShareView(camera));
const expected = decodeOk(encodeShareView(camera)).state;
const futureKind = new WireWriter();
futureKind.bytes.push(...base);
futureKind.uintField(1, 7);
futureKind.bytesField(60, [1, 2, 3]);
const kindResult = decodeOk(hashOf(futureKind.finish()));
assert.deepEqual(kindResult.state, expected);
assert.equal(kindResult.fallback, 'unknown-view-kind');
const missing = new WireWriter();
missing.bytes.push(...base);
missing.uintField(1, 1);
assert.equal(decodeOk(hashOf(missing.finish())).fallback, 'missing-thing');
});
test('an unknown or invalid entity falls back to the camera its fix implies', () => {
const fix = { lat: 35.6762, lon: 139.6503, alt: 900, observedAtMs: null };
const offset = { range: 3000, heading: 90, pitch: -40 };
const thingOf = (write) => {
const thing = new WireWriter();
write(thing);
thing
.uintField(5, offset.range)
.sintField(6, offset.heading)
.sintField(7, offset.pitch)
.sintField(8, Math.round(fix.lat * 1e4))
.sintField(9, Math.round(fix.lon * 1e4))
.sintField(10, fix.alt);
const writer = new WireWriter();
writer.bytes.push(1);
writer.bytesField(13, thing.finish());
return decodeOk(hashOf(writer.finish()));
};
const expected = normalizeShareViewState(followCameraFor(fix, offset));
// A newer app's entity kind: its id field is unknown here.
const future = thingOf((thing) => thing.textField(30, 'cam-77'));
assert.equal(future.fallback, 'unknown-entity-kind');
assert.deepEqual(future.state, expected);
assert.deepEqual(future.unknownFields, [
{ scope: 'thing', field: 30, wireType: 2 },
]);
const none = thingOf(() => {});
assert.equal(none.fallback, 'missing-entity-kind');
assert.deepEqual(none.state, expected);
const badId = thingOf((thing) => thing.uintField(1, 0x1000000));
assert.equal(badId.fallback, 'invalid-entity-id');
assert.deepEqual(badId.state, expected);
});
test('enum names outside the code tables travel as text', () => {
const hash = encodeShareView({
hudVariant: 'future-hud',
mapStack: 'future-map',
});
const { state, unknownFields } = decodeOk(hash);
assert.equal(state.hudVariant, 'future-hud');
assert.equal(state.mapStack, 'future-map');
assert.equal(unknownFields, undefined);
const style = new WireWriter();
style.bytes.push(1);
style.textField(14, 'thermal');
assert.equal(decodeOk(hashOf(style.finish())).state.style, 'thermal');
const unknownStyle = new WireWriter();
unknownStyle.bytes.push(1);
unknownStyle.textField(14, 'sepia');
const result = decodeOk(hashOf(unknownStyle.finish()));
assert.equal(result.state.style, 'normal');
assert.deepEqual(result.unknownFields, [
{ scope: 'enum:style', code: 'sepia' },
]);
});
test('links to the retired Snow style open as Normal', () => {
const snowCode = SHARE_VIEW_ENUMS.style.indexOf('snow');
assert.equal(snowCode, 6, 'a retired code is never reused');
const params = new WireWriter()
.varint(LAYER_STATE_TOKEN_ALPHABET.indexOf('d'))
.varint(90)
.varint(LAYER_STATE_TOKEN_ALPHABET.indexOf('w'))
.varint(20)
.finish();
for (const writeStyle of [
(writer) => writer.uintField(14, snowCode),
(writer) => writer.textField(14, 'snow'),
]) {
const writer = new WireWriter();
writer.bytes.push(1);
writeStyle(writer);
writer.bytesField(27, params);
const { state, unknownFields } = decodeOk(hashOf(writer.finish()));
assert.deepEqual(state, normalizeShareViewState({}));
assert.equal(unknownFields, undefined);
}
});
/* ---------------- integrity ---------------- */
test('malformed input is rejected with a reason, never thrown', () => {
const good = payloadBytes(
encodeShareView({
lat: 10,
lon: 20,
layerState: { enabledLayerIds: ['flights'] },
}),
);
const cases = {
'not-a-string': [null, undefined, 42, {}],
'too-long': ['A'.repeat(MAX_SHARE_VIEW_CHARS + 1)],
'bad-base64': ['AQ==', 'A*Q', 'AQAAA', 's=AQc', '#AQc'],
empty: [''],
// A lone byte has no room for a check byte.
truncated: ['AQ'],
checksum: [
encodeBase64Url([1, crc8([1]) ^ 1]),
encodeBase64Url([...good, (crc8(good) + 1) % 256]),
],
'unsupported-version': [hashOf([0]), hashOf([2, 8, 1])],
'truncated ': [
hashOf(good.slice(0, good.length - 1)),
hashOf([1, 0x10]), // field 2 varint with no value
hashOf([1, 0x52, 0x7f, 1]), // field 10 claims 127 bytes
hashOf([1, 0x52, 0xff, 0xff, 0xff, 0x7f]), // absurd length
],
'varint-overflow': [
hashOf([1, 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01]),
],
'non-canonical-varint': [hashOf([1, 0x10, 0x82, 0x00])],
'bad-wire-type': [hashOf([1, 0x13]), hashOf([1, 0x16])],
'bad-field-number': [hashOf([1, 0x00])],
'wire-type-mismatch': [
hashOf([1, 0x12, 0x01, 0x00]),
hashOf([1, 0x50, 0x01]),
],
'duplicate-field': [hashOf([1, 0x10, 0x02, 0x10, 0x04])],
'duplicate-entry': [hashOf([1, 0x62, 0x02, 0x31, 0x31])],
'out-of-range': [
hashOf([1, ...new WireWriter().sintField(2, 900001).finish()]),
],
'bad-text': [
hashOf([1, ...new WireWriter().bytesField(14, [0xc3, 0xa9]).finish()]),
],
};
for (const [label, inputs] of Object.entries(cases)) {
const reason = label.trim();
for (const input of inputs) {
const result = decodeShareView(input, { nowMs: NOW_MS });
assert.deepEqual(
result,
{ ok: false, reason },
`${reason}: ${String(input).slice(0, 40)}`,
);
}
}
});
test('every truncation and single-byte corruption of real payloads is handled', () => {
const random = rng(7);
for (let i = 0; i < 60; i += 1) {
const bytes = payloadBytes(encodeShareView(randomState(random)));
for (let cut = 1; cut < bytes.length; cut += 1) {
const result = decode(hashOf(bytes.slice(0, cut)));
assert.equal(typeof result.ok, 'boolean');
}
for (let j = 0; j < 40; j += 1) {
const corrupt = Uint8Array.from(bytes);
corrupt[1 + Math.floor(random() * (corrupt.length - 1))] = Math.floor(
random() * 256,
);
const result = decode(hashOf(corrupt));
assert.equal(typeof result.ok, 'boolean');
if (result.ok)
assert.deepEqual(normalizeShareViewState(result.state), result.state);
}
}
});
/* ---------------- sizes ---------------- */
test('size: a camera-only place view', () => {
const hash = assertRoundTrip({
lat: 48.8584,
lon: 2.2945,
alt: 1200,
heading: 120,
pitch: -25,
});
assert.equal(hash.length, 24);
const copied = assertRoundTrip({
lat: 48.8584,
lon: 2.2945,
alt: 1200,
heading: 120,
pitch: -25,
sharedAtMs: NOW_MS,
});
assert.equal(copied.length, 31);
});
test('size: following an aircraft', () => {
const layerState = createDefaultLayerState();
layerState.enabledLayerIds = ['flights'];
layerState.options.flights.models3d = true;
const view = (offset) => ({
kind: 'thing',
entity: { kind: 'aircraft', id: 'a1b2c3' },
offset,
lastKnown: {
lat: 37.6213,
lon: -122.379,
alt: 3200,
observedAtMs: NOW_MS - 4000,
},
});
// The app's own follow camera looks north at −35°; only the range is new.
const followed = assertRoundTrip({
view: view({ range: 5800, heading: 0, pitch: -35 }),
layerState,
sharedAtMs: NOW_MS,
});
assert.equal(followed.length, 44);
// Made by the share button: the fix is as of the link, so it has no age.
const fresh = view({ range: 5800, heading: 0, pitch: -35 });
fresh.lastKnown.observedAtMs = NOW_MS;
assert.equal(
assertRoundTrip({ view: fresh, layerState, sharedAtMs: NOW_MS }).length,
42,
);
// In Noir with Noir's own look, only the style is added.
assert.equal(
assertRoundTrip({
...SHARE_VIEW_STYLE_DEFAULTS.noir,
style: 'noir',
view: fresh,
layerState,
sharedAtMs: NOW_MS,
}).length,
44,
);
// After orbiting the aircraft, heading and pitch are written too.
const orbited = assertRoundTrip({
view: view({ range: 1500, heading: 30, pitch: -20 }),
layerState,
sharedAtMs: NOW_MS,
});
assert.equal(orbited.length, 50);
// No camera is stored: it is where following the fix puts it.
const { state } = decodeOk(orbited);
assert.deepEqual(
{
lat: state.lat,
lon: state.lon,
alt: state.alt,
heading: state.heading,
pitch: state.pitch,
},
{ lat: 37.6103, lon: -122.387, alt: 3713, heading: 30, pitch: -20 },
);
});
test('the follow offset converts to and from east/north/up metres', () => {
for (const offset of [
{ range: 5800, heading: 0, pitch: -35 },
{ range: 1500, heading: 30, pitch: -20 },
{ range: 750000, heading: 225, pitch: -60 },
{ range: 300, heading: 359, pitch: 10 },
])
assert.deepEqual(followOffsetFromEnu(followOffsetToEnu(offset)), offset);
const { east, north, up } = followOffsetToEnu({
range: 1000,
heading: 90,
pitch: -30,
});
// Looking east and down: the camera is west of the entity and above it.
assert.ok(east < -800 && Math.abs(north) < 1e-9 && Math.abs(up - 500) < 1e-9);
assert.equal(followOffsetFromEnu({ east: 0, north: 0, up: 0 }), null);
});
test('the derived camera looks at the fix from the offset, even from orbit', () => {
const R = 6_371_000;
const ecef = ({ lat, lon, alt }) => {
const r = R + alt;
const c = Math.cos((lat * Math.PI) / 180);
return [
r * c * Math.cos((lon * Math.PI) / 180),
r * c * Math.sin((lon * Math.PI) / 180),
r * Math.sin((lat * Math.PI) / 180),
];
};
for (const [fix, offset] of [
// A satellite followed from the app's own distance.
[
{ lat: 51.6, lon: -0.1, alt: 420_000 },
{ range: 750_000, heading: 225, pitch: -35 },
],
// Near a pole, and across the antimeridian.
[
{ lat: 89.9, lon: 179.95, alt: 10_000 },
{ range: 20_000, heading: 90, pitch: -20 },
],
[
{ lat: 37.6213, lon: -122.379, alt: 3200 },
{ range: 5800, heading: 0, pitch: -35 },
],
]) {
const camera = followCameraFor(fix, offset);
const [a, b] = [ecef(camera), ecef(fix)];
const distance = Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
assert.ok(Math.abs(distance - offset.range) < 1e-3 * offset.range);
assert.ok(Math.abs(camera.lon) <= 180 && Math.abs(camera.lat) <= 90);
// Short ranges look along the offset itself.
if (offset.range < 10_000) {
assert.ok(Math.abs(camera.heading - offset.heading) < 0.1);
assert.ok(Math.abs(camera.pitch - offset.pitch) < 0.1);
}
}
});
test('a fix without its own time is as of the link', () => {
const view = {
kind: 'thing',
entity: { kind: 'satellite', id: '25544' },
lastKnown: { lat: 1, lon: 2, alt: 420000 },
};
const state = normalizeShareViewState({ view, sharedAtMs: NOW_MS });
assert.equal(state.view.lastKnown.observedAtMs, NOW_MS);
assertRoundTrip({ view, sharedAtMs: NOW_MS });
// With no created-at time either, the fix has no time.
assert.equal(
decodeOk(encodeShareView({ view })).state.view.lastKnown.observedAtMs,
null,
);
});
test('the check byte rejects every single-character typo', () => {
const ALPHABET =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
const random = rng(0xc0de);
let typos = 0;
for (let i = 0; i < 40; i += 1) {
const code = encodeShareView(randomState(random));
for (let at = 0; at < code.length; at += 1) {
for (const char of ALPHABET) {
if (char === code[at]) continue;
const typo = code.slice(0, at) + char + code.slice(at + 1);
const result = decode(typo);
assert.equal(result.ok, false, `${code} → ${typo}`);
typos += 1;
}
}
}
assert.ok(typos > 50_000);
});
/* ---------------- review regressions ---------------- */
test('unknown detection names fall back to their defaults and are reported', () => {
const writer = new WireWriter();
writer.bytes.push(1);
writer.textField(20, 'FUTURE').textField(22, 'GREEDY');
const result = decodeOk(hashOf(writer.finish()));
assert.equal(result.state.detectionMode, 'DENSE');
assert.equal(result.state.detectionAllocation, 'ELASTIC');
assert.deepEqual(result.unknownFields, [
{ scope: 'enum:detectionMode', code: 'FUTURE' },
{ scope: 'enum:detectionAllocation', code: 'GREEDY' },
]);
});
test('extreme and negative-zero numbers normalize to values the decoder returns', () => {
assert.ok(Number.isFinite(normalizeShareViewState({ lon: 1e305 }).lon));
assertRoundTrip({ lon: 1e305, lat: -1e305 });
for (const state of [
{ lat: -0.00001 },
{ lon: -0.00001 },
{ alt: -0.2 },
{ pitch: -0.2 },
]) {
assertRoundTrip(state);
for (const value of Object.values(normalizeShareViewState(state)))
assert.equal(Object.is(value, -0), false);
}
});
test('future panel values and style tokens are skipped, not applied', () => {
const panels = new WireWriter();
panels.bytes.push(1);
panels.bytesField(
12,
new WireWriter()
.varint(3 * 16 + 3 * 4)
.varint(0 * 16 + 2 * 4 + 3)
.finish(),
);
const panelResult = decodeOk(hashOf(panels.finish()));
const ids = panelResult.state.panelState.specs.map((spec) => spec.id);
assert.equal(ids.includes('cctv-panel'), false);
assert.equal(ids.includes('control-panel'), false);
assert.equal(panelResult.unknownFields.length, 2);
const style = new WireWriter();
style.bytes.push(1);
style.uintField(14, 3); // thermal
style.bytesField(
27,
new WireWriter().varint(36).varint(5).varint(37).varint(6).finish(),
);
const styleResult = decodeOk(hashOf(style.finish()));
// Unknown tokens change nothing: the style keeps its own parameters.
assert.deepEqual(
styleResult.state.styleParams,
SHARE_VIEW_STYLE_DEFAULTS.thermal.styleParams,
);
assert.equal(styleResult.unknownFields.length, 2);
});
test('a layer option filed under a mirror layer is ignored', () => {
const writer = new WireWriter();
writer.bytes.push(1);
const military = layerTokenNumber('m');
writer.bytesField(10, [0, 0, 0b01000000]); // m = 22
writer.bytesField(
11,
new WireWriter()
.varint(military)
.varint(layerTokenNumber('m') * 2) // option `m` (models3dMode) as integer
.varint(0)
.finish(),
);
const result = decodeOk(hashOf(writer.finish()));
assert.deepEqual(result.state.layerState.enabledLayerIds, ['military']);
assert.deepEqual(result.unknownFields, [
{ scope: 'layer-option', code: `${military}.${layerTokenNumber('m')}` },
]);
});