import type { DeviceModelDefinition, ObservationSourceAvailability, ObservationSourceDelivery, ObservationSourceDescriptor, ObservationSourceProvider, } from "@mission-core/plugin-sdk"; import { confirmedRuntimeSourceMode, effectiveAcquisition } from "./lifecycle"; import { xgridsK1Manifest } from "./manifest"; import type { XgridsCameraPreviewDelivery, XgridsK1State, XgridsSensorCatalogStream, } from "./api"; function providerFor( state: XgridsK1State, activeModel: DeviceModelDefinition, ): ObservationSourceProvider { return { pluginId: xgridsK1Manifest.metadata.id, pluginVersion: xgridsK1Manifest.metadata.version, modelId: state.device_ref?.model_id || activeModel.id, compatibilityProfileId: state.device_session?.compatibility_profile_id ?? state.compatibility?.profile_id ?? null, }; } function bindingFor(state: XgridsK1State) { const acquisition = effectiveAcquisition(state); return { deviceId: state.device_ref?.device_id ?? null, deviceSessionId: state.device_session?.device_session_id ?? null, acquisitionId: acquisition?.acquisition_id ?? null, }; } function catalogDeclares(state: XgridsK1State, streamId: string): boolean { return Boolean(state.sensor_catalog?.streams?.some((stream) => stream.stream_id === streamId)); } function spatialAvailability(state: XgridsK1State): ObservationSourceAvailability { const mode = confirmedRuntimeSourceMode(state); if (mode !== "idle" && state.rerun_grpc_url?.trim()) return "streaming"; if (state.rerun_grpc_url?.trim()) return "available"; if (state.device_session?.connectivity === "degraded") return "degraded"; if (state.device_session?.connectivity === "connected") return "available"; return catalogDeclares(state, "spatial.point-cloud.live") ? "declared" : "unavailable"; } function catalogAvailability(value: string | null | undefined): ObservationSourceAvailability { switch (value?.trim().toLowerCase()) { case "observed": case "available": return "available"; case "connecting": return "connecting"; case "streaming": return "streaming"; case "degraded": return "degraded"; case "unavailable": return "unavailable"; case "error": return "error"; case "declared": return "declared"; default: return "unverified"; } } function cameraCatalogEntry(stream: XgridsSensorCatalogStream): boolean { const sourceId = stream.source_id?.trim(); const semanticChannelId = stream.semantic_channel_id?.trim(); if (!sourceId || !semanticChannelId) return false; const modality = stream.modality?.trim().toLowerCase(); const sensorKind = stream.sensor_kind?.trim().toLowerCase(); return ( modality === "encoded-video" || modality === "video" || sensorKind === "camera" || semanticChannelId.startsWith("camera.") ); } function safeEndpointLabel(value: string | null | undefined): string | null { const label = value?.trim(); if (!label || label.length > 128) return null; if (label.includes("://") || /(?:^|\D)(?:\d{1,3}\.){3}\d{1,3}(?:\D|$)/.test(label)) { return null; } return label; } const SENSITIVE_QUERY_KEYS = new Set([ "accesskey", "accesstoken", "apikey", "auth", "authorization", "clientsecret", "credential", "credentials", "password", "passwd", "passphrase", "privatekey", "psk", "pwd", "refreshtoken", "secret", "sessionkey", "token", "user", "username", "wifipassword", ]); function decodedForInspection(value: string): string | null { let decoded = value; try { for (let depth = 0; depth < 4; depth += 1) { const next = decodeURIComponent(decoded); if (next === decoded) break; decoded = next; } } catch { return null; } return decoded; } function sensitiveQueryKey(value: string): boolean { const canonical = value.toLowerCase().replace(/[^a-z0-9]/g, ""); return SENSITIVE_QUERY_KEYS.has(canonical) || /(?:token|secret|password|passwd|passphrase|credential|credentials)$/.test(canonical); } function safeBrowserDeliveryUrl(value: string): boolean { if (value.length > 4_096 || !value.startsWith("/") || value.startsWith("//")) return false; const inspected = decodedForInspection(value); if (!inspected) return false; if ( /[\\\r\n\u0000-\u001f\u007f]/.test(inspected) || /\brtsps?\s*:/i.test(inspected) || /(?:^|\D)(?:\d{1,3}\.){3}\d{1,3}(?:\D|$)/.test(inspected) || /\[[0-9a-f:]+\]/i.test(inspected) || /(?:^|[/?#&=])[^/?#&=\s:@]+:[^/?#&\s@]+@/.test(inspected) || /\b(?:basic|bearer)\s+[a-z0-9._~+/=-]+/i.test(inspected) ) return false; const base = new URL("https://mission-core.invalid/"); let parsed: URL; let inspectedParsed: URL; try { parsed = new URL(value, base); inspectedParsed = new URL(inspected, base); } catch { return false; } if ( parsed.origin !== base.origin || inspectedParsed.origin !== base.origin || parsed.username || parsed.password || parsed.hash || inspectedParsed.username || inspectedParsed.password || inspectedParsed.hash ) return false; for (const [key] of inspectedParsed.searchParams) { if (sensitiveQueryKey(key)) return false; } return true; } function browserDelivery( value: XgridsCameraPreviewDelivery | null | undefined, ): ObservationSourceDelivery | null { const id = value?.id?.trim(); const url = value?.url?.trim(); const mediaType = value?.media_type?.trim(); if (!id || value?.kind !== "mse-fmp4-websocket" || !url || !mediaType) return null; // Browser delivery is host-owned and same-origin. A device RTSP/IP endpoint // must never cross the plugin boundary into the host descriptor catalog. if (!safeBrowserDeliveryUrl(url)) return null; if (!/^video\/mp4(?:\s*;|$)/i.test(mediaType)) return null; return { id, kind: value.kind, url, mediaType }; } function cameraAvailability( state: XgridsK1State, stream: XgridsSensorCatalogStream, selected: boolean, delivery: ObservationSourceDelivery | null, attested: boolean, ): ObservationSourceAvailability { if (!attested) return "unverified"; if (state.device_session?.connectivity === "degraded") return "degraded"; const base = catalogAvailability(stream.availability); if (!selected) return base === "streaming" || base === "connecting" ? "available" : base; const phase = state.camera_preview?.phase?.trim().toLowerCase(); if (phase === "error" || base === "error") return "error"; if (delivery && (phase === "streaming" || phase === "active" || phase === "ready")) { return "streaming"; } if (phase === "degraded") return "degraded"; return "connecting"; } export function xgridsK1ObservationSources( state: XgridsK1State, activeModel: DeviceModelDefinition, ): ObservationSourceDescriptor[] { const provider = providerFor(state, activeModel); const binding = bindingFor(state); const clockId = binding.acquisitionId ?? binding.deviceSessionId ?? binding.deviceId ?? null; const descriptorId = (sourceId: string) => `${provider.pluginId}:${provider.modelId}:${sourceId}`; const pointCloud: ObservationSourceDescriptor = { id: descriptorId("sensor.lidar.primary"), sourceId: "sensor.lidar.primary", semanticChannelId: "spatial.point-cloud.live", label: "K1 · облако точек", description: "Облако точек, поза и траектория в общей 3D-сцене", modality: "point-cloud", role: "primary", availability: spatialAvailability(state), transport: "rerun-grpc", endpointLabel: state.rerun_grpc_url?.trim() ? "Rerun gRPC" : "MQTT → Rerun", previewUrl: state.rerun_grpc_url?.trim() || null, delivery: null, activation: null, provider, binding, capabilities: { overlay: false, fullscreen: true, resizable: false, defaultVisible: true, timelineMode: "live-only", // Rerun can hold replay data, but the Mission Core host timeline is not // wired to its time controller yet. seekable: false, sessionRecording: false, clockId, spatialRegistration: "native", }, }; const cameraRows = (state.sensor_catalog?.streams ?? []).filter(cameraCatalogEntry); const sourceIdCounts = new Map(); for (const stream of cameraRows) { const sourceId = stream.source_id?.trim(); if (sourceId) sourceIdCounts.set(sourceId, (sourceIdCounts.get(sourceId) ?? 0) + 1); } const attested = Boolean(provider.compatibilityProfileId && binding.deviceSessionId); const sessionScope = binding.deviceSessionId ?? binding.deviceId ?? "unbound"; const activeSourceId = state.camera_preview?.active_source_id?.trim() ?? null; const cameras = cameraRows.flatMap((stream) => { const sourceId = stream.source_id?.trim(); const semanticChannelId = stream.semantic_channel_id?.trim(); if (!sourceId || !semanticChannelId || sourceIdCounts.get(sourceId) !== 1) return []; const rawActivation = stream.activation; const groupId = rawActivation?.group_id?.trim(); const maxActive = rawActivation?.max_active; const activationValid = Boolean( groupId && Number.isInteger(maxActive) && (maxActive ?? 0) > 0, ); const selected = Boolean( attested && activationValid && rawActivation?.selected === true && activeSourceId === sourceId, ); const activation = activationValid ? { groupId: `${provider.pluginId}:${sessionScope}:${groupId}`, maxActive: maxActive as number, selected, controllable: Boolean(attested && rawActivation?.controllable), } : null; const candidateDelivery = stream.delivery ?? state.camera_preview?.delivery; const delivery = selected ? browserDelivery(candidateDelivery) : null; const label = stream.label?.trim() || sourceId; return [{ id: descriptorId(sourceId), sourceId, semanticChannelId, label, description: "Видеоканал, опубликованный активным device-плагином", modality: "video", role: "auxiliary", availability: cameraAvailability(state, stream, selected, delivery, attested), transport: delivery ? "websocket" : "other", endpointLabel: safeEndpointLabel(stream.endpoint_label) ?? "Локальный video adapter", previewUrl: null, delivery, activation, provider, binding, capabilities: { overlay: true, fullscreen: true, resizable: true, defaultVisible: selected && Boolean(delivery), timelineMode: "live-only", seekable: false, sessionRecording: false, clockId, spatialRegistration: "unresolved", }, }]; }); return [pointCloud, ...cameras]; }