Implementation Findings for Specification 0.2Link to the section Implementation Findings for Specification 0.2

On this page

Status: A–H resolved in spec 0.2.0-draft (2026-10-15), see Resolutions; in sections F (F10 onward) and I–V, rows marked ✓ are resolved in 0.2 and the others are open for 0.3. Input to spec 0.2 (roadmap Phase 1). Each finding comes from building the reference gateway (gateway/) and the .NET client SDK (dotnet/) against draft 0.1. Each finding states the problem, what the reference implementation does today, and a proposed resolution for the Technical Working Group.

Severity: Gap means two conforming implementations can disagree or cannot interoperate. Ambiguity means the text admits more than one reading. Editorial means the wording is wrong but the intent is clear.

A. Session channel and REST (Parts 03, 05)Link to the section A. Session channel and REST (Parts 03, 05)

# Severity Finding Reference implementation Proposal
A1 ✓ Gap Part 03 §3.3 lets a client-hosted endpoint "proxy" REST calls to the device over the session channel, but no envelope type carries a request or response. A device that does not host REST therefore cannot receive commands or lease heartbeats over WSS. The gateway hosts REST itself (transports.rest.device_hosted: true). Either define request/response envelope types (method, path, body, status) for client-hosted proxying, or make device-hosted REST mandatory and drop the proxy mode for v1.
A2 ✓ Gap §5.4.2 says a device MUST "report the applied rate in the next capabilities message", but the capability document has no member for it. Reports telemetry.x-univip-gateway.applied_rate_hz and GET /config. Add telemetry.applied_rate_hz to the capabilities schema.
A3 ✓ Ambiguity With several devices multiplexed on one session (Part 03 §3.2), it is not stated whether each hello gets its own welcome or whether welcome must carry device_id. Sends one hello per device and accepts per-device welcome envelopes; a welcome without device_id applies to every device. welcome MUST carry the device_id of the hello it answers.
A4 ✓ Ambiguity ping and pong payloads are undefined. pong carries { "ref_msg_id": <ping msg_id> }. Define both payloads (empty and ref_msg_id respectively).
A5 ✓ Gap RFC 9457 type URIs are required to be "UNIVIP-specific" but none are defined. https://univip.org/problems/<slug> (e.g. not-found, forbidden-scope, lease-conflict, malformed). Publish a problem-type registry in Part 05 and pages on univip.org.
A6 ✓ Ambiguity The OpenAPI lists both 403 "Scope or lease violation" and 422 "rejected" for POST /commands. A lease problem could be either. 403 only for a missing OAuth scope; lease problems are 422 REJECTED with NO_LEASE/LEASE_SCOPE/LEASE_EXPIRED, so the client gets a command status. Scope is checked before schema validation. Restrict 403 to scope failures in the OpenAPI text.
A7 ✓ Editorial device.online/device.offline reason values are undefined. VEHICLE_UNREACHABLE, GATEWAY_SHUTDOWN. Enumerate reasons in Part 08.

B. Telemetry and events (Parts 07, 08)Link to the section B. Telemetry and events (Parts 07, 08)

# Severity Finding Reference implementation Proposal
B1 ✓ Gap §7.11 says that when the source stops the device MUST stop publishing position/motion but keep reporting, yet the telemetry schema requires power, which is then stale. Publishes link.state: LOST, vehicle_state: UNKNOWN, health.status: UNKNOWN and the last known power. Make power optional, or add age_ms to each top-level object.
B2 ✓ Ambiguity A requested rate higher than the source's sample rate cannot be honored without repeating samples, which §7.11 forbids as fabrication. Never repeats a sample, so the effective rate is the source's rate. State that the requested rate is an upper bound limited by the source.
B3 ✓ Ambiguity Replay after reconnect (§8.4) does not say what "generated during the disconnect" means for an endpoint's first ever session. First session: nothing replayed, but every ACTIVE stateful event is sent; later sessions: everything after the last event written to that endpoint, within 500 events / 15 min. Adopt this wording.
B4 ✓ Ambiguity Re-sending an ACTIVE stateful event after reconnect: same event_id (deduplicated away by a client that already had it) or a new one? Same event_id. A client that kept state drops it; one that lost state receives it. Specify same event_id.
B5 ✓ Editorial launch_position and recovery_position types are unstated. Telemetry Position object. State it in the catalog.
B6 ✓ Editorial vehicle.camera.stream.stopped reason values are undefined. NO_VIEWERS, SHUTDOWN, SOURCE_ENDED. Enumerate.

C. Media (Part 09)Link to the section C. Media (Part 09)

# Severity Finding Reference implementation Proposal
C1 ✓ Ambiguity RTCP sender reports map the RTP timestamp to the send instant in RFC 3550 stacks, not capture time, which biases sync by the encode delay. Measured +11 ms (720p, 1 viewer), +14 ms (3 viewers); within the 100 ms budget. State that send-time mapping is acceptable within the budget, or require capture-time mapping.
C2 ✓ Gap Signaling has no teardown (close) kind and no per-peer identifier, so one session cannot hold two views of the same stream or stop a view without closing the session. A new offer for the same stream on the same session replaces the old peer. Add kind: "close" and an optional webrtc_session_id.
C3 ✓ Gap Trickled candidate payloads in §9.3 omit stream_id and there is no end-of-candidates convention. Accepts a missing stream_id when the session has one peer; an empty candidate ends gathering. Require stream_id; empty candidate = end of candidates.
C4 ✓ Gap error codes do not cover media failures (unknown stream, codec mismatch, peer limit). MALFORMED for unknown stream, bad payload or no usable H.264; RATE_LIMITED at the peer limit. Add UNKNOWN_STREAM, CODEC_UNSUPPORTED, BUSY.
C5 ✓ Ambiguity Who creates the univip-telemetry data channel, and whether it is negotiated, is unstated. The client includes it in its offer; the device sends telemetry envelopes on it. Specify: client-created, in-band, ordered, reliable.
C6 ✓ Ambiguity WEBRTC_DEVICE_OFFER and the constraints member are undefined; §9.3 says WHEP is for client-hosted endpoints while the OpenAPI places it on the device surface and omits the DELETE path behind Location. WHEP on the device REST surface with DELETE .../webrtc/{resource_id}; constraints ignored; device offer not offered. Define constraints, the device-offer flow, and the DELETE resource in the OpenAPI.
C7 ✓ Gap The H.264 level is not tied to advertised resolutions. Common stacks signal level 3.1, which is wrong above 1280×720@30. Warns when a configuration exceeds level 3.1. Require the signaled level to cover the advertised resolutions.
C8 ✓ Ambiguity Part 14's "QR-encoded device timestamp" needs a QR library and a payload format. Draws a binary timecode strip (ms, frame counter, check byte) that survives H.264 and is read back in tests. Specify the strip format as the alignment-test pattern.

D. Control (Parts 06, 10, 11, 13)Link to the section D. Control (Parts 06, 10, 11, 13)

# Severity Finding Reference implementation Proposal
D1 ✓ Gap Profile N requires Profile C (Part 04 §4.1), so a vehicle with no controllable payload cannot offer vehicle.* commands at all, even if it is fully capable. Honors the dependency: such a vehicle claims at most S. Consider N requiring only S.
D2 ✓ Gap Camera leases have no capability block: clients cannot discover their maximum TTL or heartbeat interval before acquiring one. Returns them in the lease response (defaults 900 s / 10 s). Add payloads[].lease (or a top-level camera_lease) with max_ttl_s and heartbeat_interval_s.
D3 ✓ Gap Lease envelopes for vehicle leases must carry operator_user (schema) but Part 13 §13.7 limits personal data. Broadcasting them to every session leaks it. Sends lease envelopes only to the holder's sessions; GET /leases shows a caller only its own leases (administrators see all); other clients learn of control changes from vehicle.control.lease_* events. Adopt this as normative.
D4 ✓ Ambiguity §11.4: "loss of the session with the lease-holding client MUST be treated as missed heartbeats". It is unclear whether REST heartbeats still count while the session is down. Session loss starts the 3 × interval expiry timer, which REST heartbeats do not reset; restoring the session stops it. Clarify.
D5 ✓ Ambiguity max_altitude_agl_m needs terrain data that a gateway usually lacks. Estimates AGL against home elevation. State how devices without a terrain model evaluate AGL limits, or let them declare the limit unenforced.
D6 ✓ Editorial A command issued_at more than 5 s in the future (§13.5) has no fitting rejection reason. INVALID_PARAMETERS. Add CLOCK_SKEW or name INVALID_PARAMETERS.
D7 ✓ Ambiguity vehicle.hold "MUST be accepted whenever a lease is valid", including on the ground, where it has no meaning. Accepts and completes immediately without commanding the vehicle. Confirm.
D8 ✓ Ambiguity Annex A maps COMMAND_ACK to REJECTED for some results, which implies the synchronous response waits for the vehicle's acknowledgment. Dispatches to the vehicle before answering (up to 8 s): vehicle refusals are REJECTED (422); FAILED acknowledgments become ACCEPTED then FAILED. State that the synchronous answer reflects the vehicle's acknowledgment where one exists.
D9 ✓ Ambiguity The ONVIF-style gimbal model does not say what camera.gimbal.move_relative is relative to when telemetry reports a different frame. Converts the current orientation into the requested frame using the vehicle heading. Specify.

E. Capability document and schemasLink to the section E. Capability document and schemas

# Severity Finding Reference implementation Proposal
E1 ✓ Editorial transports.media requires at least one item, so a device with no video (claiming no profile) cannot describe itself truthfully. Lists WEBRTC with an empty streams array. Allow an empty list when profiles is empty.
E2 ✓ Ambiguity schemas should list the envelope schema? Part 06 is silent. Lists univip.envelope.v0.1. State it.

F10–F21 come from the validation against PX4 1.17.0 and ArduCopter 4.7.2 SITL (docs/sitl-validation.md).

# Severity Finding Reference implementation Proposal
F1 ✓ Ambiguity A.2.2 never yields RECOVERED, and calls the landing state RECOVERING where MAVLink's MAV_LANDED_STATE is LANDING. RECOVERED while armed on the ground after a flight since arming, otherwise PREDEPLOY; LANDING → RECOVERING. Add the RECOVERED row and use MAVLink's names.
F2 ✓ Ambiguity The HOLDING rule names PX4 modes only. On ArduPilot a vehicle hovering at a goto target is in GUIDED, so it would read OPERATING forever. Hold modes: PX4 LOITER, POSCTL; ArduPilot LOITER, BRAKE, POSHOLD, HOLD, GUIDED. List hold modes per autopilot.
F3 ✓ Gap MAVLink's gimbal "vehicle frame" follows vehicle yaw but stays level, so it is not UNIVIP BODY; and rate_degps with an angle has no MAVLink equivalent as a slew limit. Reports EARTH whenever heading is known; commands lock pitch and roll to the horizon; rate_degps is not sent. Define BODY precisely and map MAVLink's frames explicitly.
F4 ✓ Ambiguity altitude_ellipsoid_m from GPS_RAW_INT.alt_ellipsoid is the raw receiver height and disagrees with the fused MSL altitude. Fused MSL plus the receiver's geoid offset (alt_ellipsoid − alt). State which source to use.
F5 ✓ Gap There is no MAVLink failsafe-reason message, so vehicle.navigation.returning_home.reason can only be inferred from English STATUSTEXT. Keyword matching; CLIENT when the gateway commanded the return in the last 10 s. Note the limitation; propose a MAVLink failsafe-reason message to Dronecode.
F6 ✓ Gap RTL parameters set to honor altitude_rel_m or land_on_arrival: false persist on the vehicle and change later failsafe returns. Reads the originals, writes only if needed, restores them when the commanded return ends or is rejected. Require restoration.
F7 ✓ Ambiguity The ACK timeout and retransmission policy, IN_PROGRESS progress reporting, and results 7/8 (COMMAND_LONG_ONLY/COMMAND_INT_ONLY) are unspecified. One retransmission (worst case 2 × 3 s); IN_PROGRESS = accepted; 7/8 → UNSUPPORTED_COMMAND. Specify.
F8 ✓ Ambiguity Zoom percent ↔ ratio needs the camera's maximum ratio, which CAMERA_INFORMATION does not carry, and continuous zoom carries only a direction. Linear 1× to a configured max_zoom_ratio; zoom not advertised without it; rate magnitude lost. Note; zoom ranges belong in camera definition files.
F9 ✓ Ambiguity For a ground-station-side gateway, vehicle link loss and stale telemetry are the same observation. vehicle.link.lost is not advertised by the MAVLink adapter; staleness raises vehicle.telemetry.stale. Clarify which topic a gateway raises.
F10 ✓ Gap A.2.2 mapped system_status CRITICAL to EMERGENCY, but MAVLink defines CRITICAL as "failsafe, can still navigate", and PX4 and ArduPilot report it for every failsafe, so a low-battery return read EMERGENCY with no RETURNING state or reason. EMERGENCY only for EMERGENCY/FLIGHT_TERMINATION; state follows the mode. Adopted in A.2.2.
F11 ✓ Gap PX4 1.17 ignores the frame of DO_REPOSITION and reads the altitude as AMSL, so a relative goto flew into the ground. REL converted with the home altitude; AGL rejected on PX4. Adopted in A.2.3.
F12 ✓ Gap PX4 1.17 gives failsafe causes only through its events protocol; its STATUSTEXT says "Failsafe activated" for battery and nothing for GCS link loss. Battery from level or text; PX4 link loss reads OTHER. Noted in A.2.2; OTHER is legitimate.
F13 ✓ Ambiguity hold as Hold/LOITER mode does not stop a PX4 reposition (it already flies in Hold) and makes ArduCopter descend without RC. PX4 all-NaN DO_REPOSITION; ArduCopter BRAKE. Adopted in A.2.3, with altitude held without pilot input.
F14 ✓ Gap No row for a vehicle hovering over home after a no-land return (both autopilots stay in RTL). HOLDING within 5 m of home at rest for 10 s; return completes. Row added; completion defined.
F15 ✓ Ambiguity The HOLDING rule ignored vertical speed, and set_heading completed mid-turn. Vertical speed < 0.5 m/s; yaw rate ≤ 5°/s. Adopted in A.2.2 and A.2.3.
F16 ✓ Gap GIMBAL_MANAGER_SET_PITCHYAW is absent in PX4 1.17, gimbal-control acquisition was omitted, and ArduPilot ignores a pitch/yaw pair with a NaN member. Rates via DO_GIMBAL_MANAGER_PITCHYAW; CONFIGURE first, BUSY if held elsewhere; omitted axis at its current angle. Adopted in A.2.3; the primary_control_sysid ⚠ is settled.
F17 ✓ Ambiguity PX4 AUX gimbals report limits in degrees in radian fields, as ±range while moving within ±range/2. Converted with a plausibility check. Deployment note in A.2.
F18 ✓ Ambiguity ArduCopter 4.7 renamed RTL_ALT/RTL_ALT_FINAL to RTL_ALT_M/RTL_ALT_FINAL_M (metres). Names and units by firmware version, with fallback. Listed in A.2.3.
F19 ✓ Gap Battery data can lag the first messages; a placeholder battery_pct: 0 would be fabricated. First observation held up to 3 s for battery data; power omitted otherwise. power is optional when unknown (B1).
F20 ✓ Editorial PX4 answers only the first peer of a MAVLink UDP link for its lifetime, so a gateway on an ephemeral udpout port is not answered after a restart. udpin on a fixed port. Deployment note in A.2.
F21 ✓ Ambiguity STATUSTEXT at WARNING or worse became health faults that block client commands, but autopilots send pre-arm refusals at CRITICAL. Transient faults (30 s). STATUSTEXT maps to fault events, not to the §11.2 safety faults.

G. Client side (.NET client SDK)Link to the section G. Client side (.NET client SDK)

# Severity Finding Reference implementation Proposal
G1 ✓ Gap Part 05 §5.1 restricts device_id to [A-Za-z0-9._~-] yet recommends urn:univip:…, and the schema allows :. Accepts :; REST clients escape the path segment but keep : literal. Allow : in Part 05 and say how to place a device_id in a URL.
G2 ✓ Gap Part 01 §1.4 says unknown enumeration values read as UNKNOWN, but the v0.1 schemas close their enums and Part 13 §13.6 says invalid messages are rejected. Likewise Part 01 §1.5 says clients SHOULD accept higher MINOR versions, but every schema pins schema to …v0.1. The SDK tolerates unknown enum values and higher-MINOR schema constants (reported separately from errors). Make enums open in the schemas, or state that validation ignores these two cases.
G3 ✓ Gap Command-status and lease envelopes reach a client only when its welcome.client_id equals the client_id behind its REST credential. welcome.client_id is not authenticated. Both implementations rely on the match. Bind the session's client identity to the endpoint's authenticated identity (mTLS or the token the device presented).
G4 ✓ Ambiguity Which direction of silence triggers a ping, when a ping counts as missed, and which messages may precede capabilities (ping, pong, error) are unstated. Ping after silence in either direction; close (4000) after three unanswered pings; ping, pong and error allowed before capabilities. Specify.
G5 ✓ Ambiguity An error envelope must carry device_id (schema), but a client cannot attribute a frame that is not JSON; no code exists for ordering violations. Such errors omit device_id; ordering violations use MALFORMED. Make device_id optional on error; add PROTOCOL_VIOLATION.
G6 ✓ Gap Refusing one device of a multiplexed session is unspecified. The Python gateway originally dropped the whole session (fixed). An error naming one device during the handshake leaves only that device out; it is offered again about every minute on the same session; a later welcome attaches it. Specify per-device refusal and re-offer.
G7 ✓ Ambiguity "At least 500 events or 15 minutes, whichever is smaller" is self-contradictory as a buffer requirement. The gateway keeps the last 500 events younger than 15 minutes; the SDK deduplicates over 10,000 event ids. Reword as two minimums.
G8 ✓ Gap There is no limit on altitude_rel_m, and Part 11 mentions "magnitude limits" for vehicle.move_relative that capabilities do not carry. Relative altitude compared with max_altitude_agl_m assuming flat terrain; no separate magnitude limit. Define both or drop the wording.
G9 ✓ Editorial transports.rest has no insecure flag although Part 13 §13.2 requires plaintext to be flagged. Development mode only, loopback. Add insecure to transports.rest.
G10 ✓ Ambiguity Whether expires_at bounds the start or the completion of a long command is unclear. Gateway: start only. SDK default for discrete commands: issued_at + 10 s. State that it bounds the start.

H. Media and security details (.NET gateway port)Link to the section H. Media and security details (.NET gateway port)

# Severity Finding Reference implementation Proposal
H1 ✓ Gap Part 09 does not say whether a device may be ICE-lite with host candidates only, or must gather server-reflexive and relay candidates; internet deployments need one or the other. Python: full ICE with optional STUN/TURN (ice_servers). .NET: ICE-lite, host candidates only. Require full ICE with TURN support for internet deployments; allow ICE-lite on managed networks.
H2 ✓ Gap No mandatory DTLS version, certificate algorithm or SRTP protection profile. DTLS 1.2, ECDSA P-256, SRTP_AES128_CM_HMAC_SHA1_80 (both). Name a baseline (and AEAD-GCM profiles as SHOULD).
H3 ✓ Gap No requirement to honour PLI/FIR and no maximum keyframe interval, so a late-joining viewer may wait indefinitely for a picture. .NET: keyframe on PLI/FIR (at most every 2 s), 1 s GOP. Python: aiortc defaults. Require PLI/FIR handling and a maximum keyframe interval (e.g. 2 s).
H4 ✓ Ambiguity RFC 6184 infers Baseline level 1.0 when an offer omits profile-level-id; implementations assume 42e01f. Both assume 42e01f. Say what to do.
H5 ✓ Ambiguity Backpressure on the telemetry data channel is unspecified; dropping envelopes conflicts with "reliable" (C5). Both drop above about 1 MiB buffered. Allow dropping telemetry (it is replaceable) and say so.
H6 ✓ Ambiguity Whether a view negotiated over the session channel survives a session drop is unstated. Both tear it down (NO_VIEWERS). Specify teardown.
H7 ✓ Ambiguity Whether §13.2's TLS 1.3 rule covers the OAuth token endpoint is unclear; applying it, and the device certificate, to an identity provider may fail or overshare. Both reuse the session's TLS settings. Exempt the token endpoint from TLS 1.3-only, or require it explicitly; never present the device certificate there unless the provider asks for mTLS.
H8 ✓ Gap Part 13 does not say which certificate field carries the device_id(s) for mTLS (e.g. SAN URI), so a client cannot authorize multiplexed devices from a certificate. Not enforced from certificates. Specify SAN URIs urn:univip:…, one per device.
H9 ✓ Ambiguity For optional capability members, null and absent are not defined as equivalent. Python sends null, .NET omits; both validate. State they are equivalent.

I. Client side (web viewer)Link to the section I. Client side (web viewer)

Found while building the standalone viewer (dotnet/src/Univip.Viewer.Web).

# Severity Finding Reference implementation Proposal
I1 ✓ Gap The device chooses the WHEP resource URL (Location) and nothing constrains its origin, so a client that follows it can send its credentials anywhere, including over plaintext. The SDK refuses a resource outside the REST base URL's origin; the viewer never exposes device URLs to the browser. Location relative and same-origin; clients never send credentials elsewhere.
I2 ✓ Gap WebRTC video is H.264 only and codecs cannot express VP8; browsers built without proprietary codecs (open-source Chromium) cannot show any UNIVIP video, and the refusal is a generic error. The viewer prefers H.264 and reports the device's refusal with the browser limitation. Optional VP8/AV1 beside the mandatory H.264; CODEC_UNSUPPORTED (C4).
I3 ✓ Gap Nothing says when a device reclaims a WHEP view whose client has gone (a DELETE while a page closes is unreliable). Python drops the peer when ICE fails; the viewer DELETEs 10 s after a page disappears. Release within 30 s of consent loss; clients SHOULD end views.
I4 ✓ Ambiguity No guidance on when a client shows telemetry as stale. STALE after 3 s without a sample, when offline, or while vehicle.telemetry.stale is active. Recommend max(3 / applied rate, 2 s) and showing the age.
I5 ✓ Ambiguity Which stateful events are the same condition (so a CLEARED closes the right ACTIVE) is implied, not specified. Topic plus data.code, else data.lease_id, else source.payload_id. Define the condition key in Part 08.
I6 Ambiguity Browser clients receive media directly from the device, so the device's candidates must be reachable from the browser, not only from the VMS server; who supplies TURN to client-side peers is unstated (extends H1). Host candidates only; works on one network. Open for 0.3: clients bring their own ICE servers, or devices advertise hints (media.webrtc.ice_servers).
I7 ✓ Ambiguity Annex A.2.2's HOLDING dwell is MAVLink-only; nothing prevents vehicle_state flapping (the simulator flaps HOLDING↔OPERATING after a hold). The viewer shows states as reported. Dwell in Part 07 for every device.

J. DJI Cloud API adapterLink to the section J. DJI Cloud API adapter

Found while building the second adapter (gateway/univip_gateway/adapters/dji_cloud.py, Annex A.4), against a fake dock built from DJI's public documentation.

# Severity Finding Reference implementation Proposal
J1 ✓ Gap DJI's documented state rate is 0.5 Hz (higher only in an active live-flight-controls session); Part 07 asks ≥ 2 Hz deployed, and a fixed 2 s staleness reads every gap as a loss. rate_hz.max 0.5; adapter-declared staleness (4.5 s). Protocol-limited sources declare a lower rate; staleness ≥ 2.5 periods.
J2 Gap DJI positions and targets are ellipsoidal or relative to the takeoff point; vehicle.goto and GeoPoint cannot carry an ellipsoid height, and "home/launch" in altitude_rel_m is ambiguous when home differs from the takeoff point. MSL only with a configured geoid offset; AGL targets rejected. Open for 0.3: altitude_ellipsoid_m in GeoPoint and goto; define altitude_rel_m against the launch point.
J3 Ambiguity DJI has no single hover service; the right stop depends on the active task, which 0.5 Hz state may report late. Chooses by task (Annex A.4.3). Annex note (done); recommend devices expose one brake.
J4 ✓ Gap A docked aircraft is naturally DOCKED, but take-off was allowed only from IDLE/PREDEPLOY. Reports IDLE with domain_state: DOCKED. Take-off from DOCKED when the dock reports ready.
J5 Gap DJI authority is per dock link, shared by every cloud user of the dock, so a UNIVIP lease cannot exclude non-UNIVIP users; payload authority can sit with the pilot separately while telemetry has one override flag. Lazy grab, never from the pilot; payload held by the pilot → PILOT_OVERRIDE without a camera-scoped event. Open for 0.3: per-payload control state; a capability flag that leases are not exclusive.
J6 Gap DJI offers look-at, drag, image-point aim and frame-zoom but no absolute gimbal set; UNIVIP cannot express the image-space commands. set_orientation via look-at 200 m along the line of sight. Open for 0.3: camera.gimbal.center_on, camera.zoom.to_region.
J7 Ambiguity DJI zoom_factor has no documented base field of view; wide, zoom and IR lenses share one payload. Ratio passed through; no FOV. Open for 0.3: define zoom_ratio against the widest optical FOV; model lenses.
J8 Gap The vehicle reports return-home and landing power thresholds; UNIVIP events use gateway-configured ones. Ignored. Open for 0.3: power.return_home_pct, landing_pct.
J9 ✓ Gap DJI return reasons (wind, crewed aircraft, obstacle, hardware fault) have no UNIVIP reason. OTHER. WEATHER, TRAFFIC, OBSTACLE, FAULT.
J10 Gap Telemetry has no dock object (cover, charging, wind, rain, temperature). dock.state_changed / dock.fault events; environment dropped. Open for 0.3: optional telemetry dock.
J11 Ambiguity DJI HMS alarms are numeric codes whose texts live in an external file, so the Part 08 §8.5 codes cannot be derived reliably. Keyword classification when hms_json is configured, else OTHER. Open for 0.3: a namespaced vendor_code beside the standard code.
J12 Gap The dock pushes video to a media server and carries no capture time, so §9.5 alignment can use only arrival time. Arrival-time stamping. Open for 0.3: let a stream declare receipt-time alignment with an estimated offset.
J13 Ambiguity Several DJI signs and frames are undocumented, and DJI's English and Chinese documents disagree on one height. Assumptions marked ⚠ in Annex A.4. Annex mappings state their assumptions (done); validate on hardware.
J14 ✓ Gap When home is unknown the gateway silently skipped distance-from-home and MSL↔AGL checks (fail open); with DJI this happens after every restart. Now rejects with OUT_OF_LIMITS (both gateways). Limits fail closed (Part 11 §11.2).
J15 Gap DJI take-off includes dock preparation that no documented service aborts, so vehicle.cancel before lift-off is undefined. The reference implementation now rejects vehicle.cancel with BUSY, returns 409 for REST cancellation, and reports failure if lease loss cannot stop launch preparation; hardware abort support remains unavailable. Open for 0.3: define cancel before lift-off; let devices declare take-off non-cancellable.

K. Recording and replayLink to the section K. Recording and replay

Found while building the recording test (gateway/univip_gateway/recording/, docs/recording-test.md).

# Severity Finding Reference implementation Proposal
K1 ✓ Gap §9.4 asked for KLV "synchronized by PTS" but named the asynchronous signalling (stream type 0x06 + KLVA); FFmpeg 6.1 also corrupts asynchronous KLV. Synchronous carriage (0x15, metadata descriptors, stream id 0xFC), one Local Set per frame. Require synchronous carriage.
K2 ✓ Editorial Wrong tag numbers in Annex A.3 (72 is Event Start Time, ellipsoid height is 75, 73–74 are RVT/VMTI, 92 is Angle of Attack). Ellipsoid height in 75. Correct the table.
K3 ✓ Gap Tags 18–20 are relative to the platform body; the Annex formulas ignored platform pitch and roll (about 10° off at 10° nose-down). Full Z-Y-X rotation. Specify the conversion.
K4 ✓ Ambiguity The §9.5 target compared a sample's timestamp with the frame, meaningless with interpolation and unattainable at 2 Hz with nearest-sample selection. Interpolates to the frame time; mapping error +6 to +21 ms. Target is the frame-time error; interpolate (or nearest when samples ≤ 100 ms apart).
K5 ✓ Gap §9.6's in-band alternative cannot replay vehicle state, power, link or health. Records both. TS copy replaces telemetry only for the members it carries.
K6 ✓ Gap The recording-time → device-time mapping had no defined form; PTS-keyed maps break on remux. Per-frame map plus detection of a constant shift. Implicit with per-frame tag 2; otherwise anchored to the segment's first frame.
K7 ✓ Ambiguity C1 quantified: the latest sender report alone errs by up to 71 ms; the minimum over the last eight by ≤ 21 ms. Minimum-offset estimator. Recommend it.
K8 ✓ Gap A client-estimated footprint cannot be marked as such inside ST 0601. Estimation opt-in, marked in the manifest. Do not export estimates as ST 0601 corners unless marked.
K9 Ambiguity The time scale of tag 2 (UTC/POSIX vs MISP Time) is unstated. POSIX microseconds. Open: confirm against ST 0603 in the formal review.
K10 ✓ Ambiguity No rule for offset corners (26–33) vs full corners (82–89). Offsets when all fit within ±0.075°. Rule added to Annex A.3.
K11 ✓ Editorial The ST 0601 edition (tag 65) and the content of tag 11 were unspecified. 65 = 17, 11 = payload_id. Stated in Annex A.3 (edition still to confirm).

S. Media security (internal WebRTC review)Link to the section S. Media security (internal WebRTC review)

From the internal review of the .NET WebRTC stack (docs/security/webrtc-review-2026-10.md), whose implementation findings WR-01…WR-22 are tracked there.

# Severity Finding Reference implementation Proposal
H-S1 ✓ Gap Part 09 did not require ICE consent freshness (RFC 7675), so an ICE-lite device could be made to send video to a third party. .NET sends its own consent checks; Python's ICE does natively. RFC 7675 on every device; media only with fresh consent.
H-S2 ✓ Gap No rules for the WHEP resource: who may delete it, how unguessable its id must be. Creator only, others 404; 128-bit ids (both gateways). Adopted.
H-S3 ✓ Gap No bounds on signalling input. 64 KiB offers; 413 / MALFORMED. 64 KiB cap; devices MAY bound elements further.
H-S4 ✓ Gap No per-client media limit; one client can take every peer slot. Device-wide max_peers only. Devices MAY limit views per client_id (BUSY).
H-S5 ✓ Gap No requirement to verify the DTLS certificate against a strong fingerprint. Any SHA-2 match; SHA-1 ignored; certificate per peer. SHA-256 or stronger; per-peer or rotated certificates.
H-S6 ✓ Ambiguity §13.8 logging vs media-plane noise (log flooding). DTLS failures logged; STUN noise rate-limited. Adopted in Part 13 §13.8.
H-S7 ✓ Gap Direction and limits of the univip-telemetry channel unstated. Client messages ignored; max-message-size advertised. Device-to-client only.
H-S8 ✓ Ambiguity Host candidates disclose every interface address. Both gateways offer all interfaces. Devices MAY restrict interfaces (Part 09 §9.3).
H-S9 ✓ Editorial H.264 mandated with no note on encoder licences or patents. media.encoder and THIRD_PARTY_NOTICES.md. Informative note in Part 09.

V. Implementing 0.2 (review of the 0.2 text)Link to the section V. Implementing 0.2 (review of the 0.2 text)

Found while moving the .NET SDK, gateway and viewer to 0.2.

# Severity Finding Reference implementation Proposal
V1 ✓ Gap The capabilities schema required session_proxy: true whenever device_hosted was false, so a valid 0.1 document from a device without REST failed 0.2 validation, contradicting Part 01 §1.5. — The rule applies to documents declaring v0.2 or later (schema condition on schema); Part 01 §1.5 says new MINOR requirements apply to messages declaring that MINOR.
V2 ✓ Gap Behind a client-hosted endpoint every caller reaches the device under one client_id, so the device cannot enforce creator-only deletion of views among them. The .NET endpoint documented the limitation. The client-hosted endpoint MUST enforce the rule among its own callers (Part 09 §9.3).
V3 ✓ Ambiguity Part 01 §1.4 said a command with an unknown open-enum value is rejected INVALID_PARAMETERS, while both gateways answer 400 malformed from schema validation. 400 malformed. Either is allowed (Part 01 §1.4).
V4 ✓ Editorial No problem type for 413. malformed. malformed is used with 413 (Part 05 §5.5.2).
V5 ✓ Editorial No problem type for 405. method-not-allowed (Python's proxy answered 404; fixed). method-not-allowed registered.
V6 ✓ Editorial §5.5.1 mandates a 504 from the client-hosted endpoint but no types existed for its 502/503/504. .NET used about:blank. device-error, device-unavailable, device-timeout registered; the .NET endpoint uses them.
V7 ✓ Ambiguity An answer did not reference its offer, so concurrent offers for one stream on one session could not be told apart. The .NET SDK sends offers for a stream one at a time. Answers carry ref_msg_id (both gateways).
V8 ✓ Ambiguity applied_rate_hz was "the session's rate", but requested_rate_hz sets one rate per device. Both gateways: the latest request wins. The rate is per device; every session's capabilities report it (Part 05 §5.4.2).
V9 ✓ Ambiguity No error code for a signal to a device without media. Python UNSUPPORTED_SCHEMA, .NET UNKNOWN_STREAM. UNKNOWN_STREAM (both gateways).
V10 ✓ Ambiguity webrtc_session_id had no randomness rule. Both reuse the 128-bit view resource id. At least 122 random bits (Part 09 §9.3).
V11 ✓ Editorial applied_rate_hz was served over REST by the Python gateway although the spec omits it there. Fixed; the ETag is computed over the REST document. —

Resolutions in 0.2.0-draftLink to the section Resolutions in 0.2.0-draft

Resolved rows are marked ✓. These are the editor's resolutions in the draft and remain open to review by the Technical Working Group once it is constituted.

# Resolution Where (spec part §)
A1 request/response envelopes (session proxy); mandatory for devices without a listener 05 §5.5.1, 03 §3.3, 06 §6.3
A2 telemetry.applied_rate_hz in capabilities 05 §5.4.2, 06 §6.3
A3 welcome carries the device_id of the hello it answers; one without answers all pending 05 §5.3, §5.4.2
A4 ping empty, pong with ref_msg_id 05 §5.4.5
A5 Problem-type registry; pages generated on univip.org 05 §5.5.2
A6 403 only for scope; lease problems are 422 rejections 05 §5.5, 10 §10.1, OpenAPI
A7 device.offline reasons enumerated 08 §8.3
B1 power optional when the source stops; carried-over values have age_ms 07 §7.1, §7.6, §7.11
B2 Requested rate is an upper bound; samples never repeated 07 §7.11, 05 §5.4.2
B3 Replay wording adopted (per endpoint; nothing on the first session) 08 §8.4
B4 Same event_id on re-send 08 §8.4
B5 Launch and recovery positions are telemetry position objects 08 §8.3
B6 stream.stopped reasons enumerated 08 §8.3
C1 Send-time RTCP mapping acceptable within the 100 ms budget; capture time SHOULD 09 §9.3, §9.5
C2 kind: close and webrtc_session_id 09 §9.3, envelope schema
C3 stream_id on candidates; empty candidate ends gathering 09 §9.3
C4 UNKNOWN_STREAM, CODEC_UNSUPPORTED, BUSY 05 §5.4.4, 09 §9.3
C5 Client-created, in-band, ordered, reliable 09 §9.3
C6 constraints defined; device-offer mode withdrawn; WHEP DELETE in OpenAPI 09 §9.3, 05 §5.5, OpenAPI
C7 Signaled level must cover the advertised resolutions 09 §9.2
C8 Timecode strip specified as the alignment pattern 14 §14.4
D1 Profile N requires S only 04 §4.1, §4.4, capabilities schema
D2 payloads[].lease with max_ttl_s, heartbeat_interval_s 06 §6.4, 11 §11.3.1
D3 Adopted as normative 11 §11.3.1
D4 Clarified: session loss starts the expiry clock; REST heartbeats do not reset it 11 §11.4
D5 limits.altitude_agl_basis 06 §6.5, 11 §11.2
D6 INVALID_PARAMETERS for future-dated, EXPIRED for stale issued_at 11 §11.2, 13 §13.5
D7 Confirmed 11 §11.4
D8 Synchronous answer reflects the controller's acknowledgment (≤ 10 s wait) 10 §10.1, Annex A.2.3
D9 Deltas apply to the current orientation in the requested frame 10 §10.3
E1 transports.media may be empty when no profile is claimed 06 §6.3, capabilities schema
E2 schemas lists the envelope and capabilities schemas 06 §6.3
F1 RECOVERED row; LANDING → RECOVERING Annex A.2.2
F2 Hold modes listed per autopilot Annex A.2.2
F3 BODY/EARTH defined; MAVLink vehicle frame = BODY; rate_degps is a slew limit that may be ignored 10 §10.3, Annex A.2.1
F4 Fused MSL plus geoid separation Annex A.2.1
F5 Limitation noted (STATUSTEXT inference) Annex A.2.1
F6 Restoration required Annex A.2.3
F7 ACK wait, one retransmission, IN_PROGRESS, 7/8 specified Annex A.2.3
F8 Noted; zoom advertised only with a known maximum Annex A.2.1
F9 Gateways raise telemetry.stale, not link.lost 08 §8.3, Annex A.2.3
G1 : allowed; URL placement stated 05 §5.1
G2 Open enumerations and MINOR-tolerant schema matching 01 §1.4, §1.5, 13 §13.6, schemas
G3 Verified client identity from the device's endpoint configuration 05 §5.3.1, 13 §13.4
G4 Ping, unanswered ping and pre-capabilities messages specified 05 §5.3
G5 device_id optional on unattributable errors; PROTOCOL_VIOLATION 05 §5.4, §5.4.4
G6 Per-device refusal and re-offer (≥ 60 s) specified 05 §5.3
G7 Reworded as one guarantee (≤ 15 min and ≤ 500 events) 08 §8.4
G8 Magnitude wording dropped; altitude_rel_m checked via the AGL basis 11 §11.1, §11.2
G9 transports.rest.insecure 06 §6.3, 13 §13.2
G10 expires_at bounds the start 10 §10.1
H1 Full ICE SHOULD (REQUIRED over the internet); ICE-lite declared with media.webrtc.ice 09 §9.3, 06 §6.3
H2 Baseline named (DTLS 1.2+, ECDHE, P-256, SRTP_AES128_CM_HMAC_SHA1_80; GCM SHOULD) 09 §9.3
H3 PLI/FIR answered within 1 s; key frame at least every 2 s SHOULD 09 §9.3
H4 Clients include profile-level-id; devices MAY assume 42e01f 09 §9.3
H5 Telemetry may be dropped under backpressure 09 §9.3
H6 Views end with their session 09 §9.3
H7 Token endpoint follows the provider's policy (TLS 1.2+); device certificate only for mTLS 13 §13.2
H8 SAN URI per device_id 13 §13.3
H9 null and absent are equivalent in capabilities 01 §1.4, 06 §6.3

How to use this documentLink to the section How to use this document

Findings are resolved by PRs against the specification that cite the finding number. When resolved, mark the row with the resolving version and keep it for history.

Generated from docs/implementation-findings.md.