"""VAM v3 ASN.1 UPER decoding (ETSI TS 103 300-3 V2.3.1 / CDD V2.4.1). Pinned schemas and notices are bundled in asn1/. See ../docs/vam-support.md. """ from functools import lru_cache from pathlib import Path @lru_cache(maxsize=1) def codec(): try: import asn1tools except ImportError as exc: raise ValueError("VAM decoder unavailable: install bridge/requirements.txt") from exc directory = Path(__file__).resolve().parent / "asn1" return asn1tools.compile_files([ str(directory / "TS102894-2v241-CDD.asn"), str(directory / "VAM-PDU-Descriptions.asn"), ], codec="uper") def json_value(value): """Preserve ASN.1 CHOICE/bit-string values in JSON-safe form.""" if isinstance(value, bytes): return value.hex() if isinstance(value, dict): return {k: json_value(v) for k, v in value.items()} if isinstance(value, (tuple, list)): return [json_value(v) for v in value] return value def decode_vam(data): if not data or len(data) > 4096: raise ValueError("empty/oversized VAM") decoder = codec() import asn1tools try: message = decoder.decode("VAM", data, check_constraints=True) # Detect trailing bytes, nonzero padding and unknown extensions that the # library may skip. Do not silently call partially understood data valid. if decoder.encode("VAM", message, check_constraints=True) != data: raise ValueError("noncanonical VAM or unsupported extension/trailing data") except (asn1tools.DecodeError, asn1tools.EncodeError, asn1tools.ConstraintsError) as exc: raise ValueError("invalid VAM UPER: " + str(exc)) from exc header = message["header"] if header["protocolVersion"] != 3 or header["messageId"] != 16: raise ValueError("unsupported VAM protocol version/message ID") body = message["vam"] containers = body["vamParameters"] basic = containers["basicContainer"] pos = basic["referencePosition"] hf = containers["vruHighFrequencyContainer"] result = dict(station_id=header["stationId"], generation_delta_time=body["generationDeltaTime"], station_type=basic["stationType"], containers=json_value(containers)) lat, lon = pos["latitude"], pos["longitude"] if lat != 900000001 and lon != 1800000001: result.update(lat=lat / 1e7, lon=lon / 1e7) altitude = pos["altitude"]["altitudeValue"] if altitude != 800001: result["altitude_m"] = altitude / 100 heading = hf["heading"]["value"] if heading != 3601: result["heading_deg"] = heading / 10 speed = hf["speed"]["speedValue"] if speed != 16383: result["speed_mps"] = speed / 100 acceleration = hf["longitudinalAcceleration"]["longitudinalAccelerationValue"] if acceleration != 161: result["longitudinal_acceleration_mps2"] = acceleration / 10 return result