#!/usr/bin/env python3 """Deterministically generate the small language-neutral Phase 3A bindings.""" from __future__ import annotations import argparse import hashlib import json import pathlib import re import subprocess import sys from typing import Any ROOT = pathlib.Path(__file__).resolve().parents[1] SCHEMA_PATH = ROOT / "schemas/control-v1.schema.json" VERSION_PATH = ROOT / "VERSION" COMPATIBILITY_PATH = ROOT / "compatibility.json" def load_schema() -> tuple[dict[str, Any], str, str, dict[str, Any]]: raw = SCHEMA_PATH.read_bytes() schema = json.loads(raw) version = VERSION_PATH.read_text(encoding="utf-8").strip() if not re.fullmatch(r"\d+\.\d+\.\d+", version): raise ValueError("VERSION must be semantic version text") compatibility = json.loads(COMPATIBILITY_PATH.read_text(encoding="utf-8")) for key in ("current", "n_minus_1", "n_minus_2"): if not isinstance(compatibility.get(key), str): raise ValueError("compatibility declaration is incomplete") return schema, hashlib.sha256(raw).hexdigest(), version, compatibility def pascal(name: str) -> str: return "".join(part[:1].upper() + part[1:] for part in re.split(r"[_-]", name)) def go_field(name: str) -> str: initialisms = {"api": "API", "http": "HTTP", "id": "ID", "ip": "IP", "sha": "SHA", "ttl": "TTL", "url": "URL", "uuid": "UUID"} return "".join( initialisms.get(part, part[:1].upper() + part[1:]) for part in re.split(r"[_-]", name) ) def camel(name: str) -> str: parts = re.split(r"[_-]", name) return parts[0] + "".join(part[:1].upper() + part[1:] for part in parts[1:]) def swift_field(name: str) -> str: value = camel(name) if value in {"protocol", "class", "struct", "enum", "extension", "private", "public", "internal", "fileprivate", "open", "func", "let", "var", "import", "switch", "case", "default", "operator", "where", "repeat", "return", "throw", "throws", "try", "catch", "defer", "in", "is", "as"}: return value + "Value" return value RUST_KEYWORDS = { "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return", "self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe", "use", "where", "while", "async", "await", "dyn", "abstract", "become", "box", "do", "final", "macro", "override", "priv", "typeof", "unsized", "virtual", "yield", "try", "union", } def rust_field(name: str) -> str: value = camel(name) return value + "Value" if value in RUST_KEYWORDS else value def ref_name(value: Any) -> str | None: if isinstance(value, dict) and isinstance(value.get("$ref"), str): return value["$ref"].split("/")[-1] return None def prop_type(prop: dict[str, Any], language: str) -> str: reference = ref_name(prop) if reference: return reference if language == "go" else (reference if language == "rust" else reference) if prop.get("type") == "array": item = prop.get("items", {}) item_type = prop_type(item, language) if language == "go": return f"[]{item_type}" if language == "rust": return f"Vec<{item_type}>" return f"[{item_type}]" if prop.get("type") == "integer": return "int64" if language == "go" else ("i64" if language == "rust" else "Int64") if prop.get("type") == "boolean": return "bool" if language != "swift" else "Bool" if prop.get("type") == "object": return "map[string]any" if language == "go" else ("JsonObject" if language == "rust" else "JSONObject") return "string" if language == "go" else ("String" if language == "rust" else "String") def go_zero(prop: dict[str, Any], field: str) -> str: typ = prop_type(prop, "go") if typ in {"string", "int64", "bool"}: zero = {"string": '""', "int64": "0", "bool": "false"}[typ] return f"v.{field} == {zero}" if typ.startswith("[]"): return f"v.{field} == nil" if typ == "map[string]any": return f"v.{field} == nil" return f"reflect.DeepEqual(v.{field}, {typ}{{}})" def go_validation(definition: dict[str, Any]) -> list[str]: lines: list[str] = [] name = definition["name"] required = set(definition.get("required", [])) for prop_name, prop in definition.get("properties", {}).items(): field = go_field(prop_name) required_zero_is_valid = prop.get("type") == "integer" and prop.get("minimum") == 0 if prop_name in required and prop.get("type") != "boolean" and not required_zero_is_valid: zero = go_zero(prop, field) lines.append(f"\tif {zero} {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"required\"}}) }}") if prop.get("type") == "string": if "minLength" in prop: lines.append(f"\tif len(v.{field}) < {prop['minLength']} && v.{field} != \"\" {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"min_length\"}}) }}") if "maxLength" in prop: lines.append(f"\tif len(v.{field}) > {prop['maxLength']} {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"max_length\"}}) }}") if "x-max-bytes" in prop: lines.append(f"\tif len(v.{field}) > {prop['x-max-bytes']} {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"max_bytes\"}}) }}") if "const" in prop: lines.append(f"\tif v.{field} != \"{prop['const']}\" && v.{field} != \"\" {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"invalid_value\"}}) }}") if "enum" in prop: allowed = " || ".join(f'v.{field} == "{value}"' for value in prop["enum"]) lines.append(f"\tif v.{field} != \"\" && !({allowed}) {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"invalid_value\"}}) }}") if prop.get("format") == "date-time": lines.append( '\tif v.%s != "" { if parsed, err := time.Parse(time.RFC3339Nano, v.%s); err != nil || parsed.UTC().Format(time.RFC3339Nano) != v.%s { violations = append(violations, FieldViolation{Field: "%s", Code: "invalid_time"}) } }' % (field, field, field, prop_name) ) if prop.get("format") == "base64url": lines.append(f"\tif v.{field} != \"\" {{ if _, err := base64.RawURLEncoding.Strict().DecodeString(v.{field}); err != nil {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"invalid_format\"}}) }} }}") if prop.get("type") == "integer": if "minimum" in prop: lines.append(f"\tif v.{field} != 0 && v.{field} < {prop['minimum']} {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"minimum\"}}) }}") if "maximum" in prop: lines.append(f"\tif v.{field} > {prop['maximum']} {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"maximum\"}}) }}") if prop.get("type") == "array": if "minItems" in prop: lines.append(f"\tif len(v.{field}) < {prop['minItems']} {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"min_items\"}}) }}") if "maxItems" in prop: lines.append(f"\tif len(v.{field}) > {prop['maxItems']} {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"max_items\"}}) }}") items = prop.get("items", {}) if "enum" in items: allowed = " || ".join(f'item == "{value}"' for value in items["enum"]) lines.append(f"\tfor _, item := range v.{field} {{ if !({allowed}) {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"invalid_item\"}}) }} }}") if prop.get("uniqueItems") and items.get("type") == "string": lines.append(f"\tfor index, item := range v.{field} {{ for prior := 0; prior < index; prior++ {{ if item == v.{field}[prior] {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"duplicate_item\"}}) }} }} }}") item_ref = ref_name(items) if item_ref: lines.append(f"\tfor index := range v.{field} {{ if err := v.{field}[index].Validate(); err != nil {{ violations = append(violations, FieldViolation{{Field: fmt.Sprintf(\"{prop_name}[%d]\", index), Code: \"invalid_item\"}}) }} }}") reference = ref_name(prop) if reference: validation = f"if err := v.{field}.Validate(); err != nil {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"invalid_object\"}}) }}" if prop_name not in required: lines.append(f"\tif v.{field} != nil {{ {validation} }}") else: lines.append(f"\t{validation}") if name in {"AllocationPolicy", "ManifestBounds"}: lines.append("\tif v.MinimumKbps > v.TargetKbps || v.TargetKbps > v.MaximumKbps { violations = append(violations, FieldViolation{Field: \"bounds\", Code: \"invalid_order\"}) }") if name == "GatewayRegistration": lines.append("\tif v.ProtocolMinVersion > v.ProtocolMaxVersion { violations = append(violations, FieldViolation{Field: \"protocol_version\", Code: \"invalid_order\"}) }") if name == "ChannelFrame": lines.append("\tif v.FragmentIndex >= v.FragmentCount { violations = append(violations, FieldViolation{Field: \"fragment_index\", Code: \"invalid_order\"}) }") return lines def generate_go(defs: dict[str, dict[str, Any]], schema_hash: str, version: str, compatibility: dict[str, Any]) -> str: out = [ "// Code generated by tools/generate.py; DO NOT EDIT.", "package protocol", "", "import (", "\"bytes\"", "\"encoding/base64\"", "\"encoding/json\"", "\"errors\"", "\"fmt\"", "\"reflect\"", "\"strings\"", "\"time\"", ")", "", f'const SchemaSHA256 = "{schema_hash}"', f'const ProtocolVersion = "{version}"', f'const CurrentWireVersion = "{compatibility["current"]}"', f'const NMinus1WireVersion = "{compatibility["n_minus_1"]}"', f'const NMinus2WireVersion = "{compatibility["n_minus_2"]}"', "", "type FieldViolation struct {", "\tField string `json:\"field\"`", "\tCode string `json:\"code\"`", "}", "", "type ValidationError struct {", "\tViolations []FieldViolation", "}", "", "func (e ValidationError) Error() string { return \"protocol validation failed\" }", "", ] for name in sorted(defs): if name == "FieldViolation": continue definition = defs[name] out.append(f"type {name} struct {{") required = set(definition.get("required", [])) for prop_name, prop in definition.get("properties", {}).items(): tag = prop_name + (",omitempty" if prop_name not in required else "") typ = prop_type(prop, "go") if prop_name not in required and ref_name(prop): typ = "*" + typ out.append(f"\t{go_field(prop_name)} {typ} `json:\"{tag}\"`") out.extend(["}", ""]) for name in sorted(defs): out.append(f"func (v {name}) Validate() error {{") out.append("\tvar violations []FieldViolation") out.extend(go_validation(defs[name])) out.append("\tif len(violations) > 0 { return ValidationError{Violations: violations} }") out.append("\treturn nil") out.append("}") out.append("") out.append(f"func Decode{name}(data []byte) ({name}, error) {{") out.append(f"\tvar value {name}") out.append("\tif len(data) > 1024*1024 { return value, errors.New(\"protocol payload exceeds limit\") }") out.append("\tvar fields map[string]json.RawMessage") out.append("\tif err := json.Unmarshal(data, &fields); err != nil { return value, err }") required_fields = sorted(defs[name].get("required", [])) for prop_name in required_fields: out.append( '\tif raw, ok := fields["%s"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { return value, ValidationError{Violations: []FieldViolation{{Field: "%s", Code: "required"}}} }' % (prop_name, prop_name) ) for prop_name, prop in defs[name].get("properties", {}).items(): if prop_name not in required_fields and ref_name(prop): out.append( '\tif raw, ok := fields["%s"]; ok && bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { return value, ValidationError{Violations: []FieldViolation{{Field: "%s", Code: "invalid_object"}}} }' % (prop_name, prop_name) ) if "x-max-bytes" in prop and prop.get("type") != "string": out.append( '\tif raw, ok := fields["%s"]; ok && len(raw) > %d { return value, ValidationError{Violations: []FieldViolation{{Field: "%s", Code: "max_bytes"}}} }' % (prop_name, prop["x-max-bytes"], prop_name) ) out.append("\tdecoder := json.NewDecoder(bytes.NewReader(data))") out.append("\tdecoder.DisallowUnknownFields()") out.append("\tif err := decoder.Decode(&value); err != nil { return value, err }") out.append("\tvar trailing any") out.append("\tif err := decoder.Decode(&trailing); err != io.EOF { if err == nil { return value, errors.New(\"trailing JSON value\") }; return value, err }") out.append("\tif err := value.Validate(); err != nil { return value, err }") out.append("\treturn value, nil") out.append("}") out.append("") out.append(f"func Encode{name}(value {name}) ([]byte, error) {{") out.append("\tif err := value.Validate(); err != nil { return nil, err }") out.append("\treturn json.Marshal(value)") out.append("}") out.append("") out.extend([ "var ErrNoCapabilityOverlap = errors.New(\"no capability overlap\")", "", "func IntersectCapabilityProfiles(profiles ...CapabilityProfile) (CapabilityProfile, error) {", "\tif len(profiles) == 0 { return CapabilityProfile{}, ErrNoCapabilityOverlap }", "\tselected := profiles[0]", "\tif err := selected.Validate(); err != nil { return CapabilityProfile{}, ErrNoCapabilityOverlap }", "\tcommon := append([]string(nil), selected.ClientDecode...)", "\tfor _, profile := range profiles[1:] {", "\t\tif err := profile.Validate(); err != nil || profile.Transport != selected.Transport || profile.Framing != selected.Framing || profile.Media != selected.Media || profile.Audio != selected.Audio || profile.SourceRateControl != selected.SourceRateControl { return CapabilityProfile{}, ErrNoCapabilityOverlap }", "\t\tnext := common[:0]", "\t\tfor _, candidate := range common { for _, offered := range profile.ClientDecode { if candidate == offered { next = append(next, candidate); break } } }", "\t\tcommon = next", "\t\tif len(common) == 0 { return CapabilityProfile{}, ErrNoCapabilityOverlap }", "\t}", "\tselected.ClientDecode = common", "\treturn selected, nil", "}", "", ]) out.extend([ "func (v TunnelAdmissionRequest) DeviceAdmissionTranscript() []byte {", "\tfields := []string{v.SessionID, v.GatewayID, v.Audience, v.Grant, fmt.Sprintf(\"%d\", v.ReconnectSequence), v.ClientNonce, v.Capabilities.Transport, v.Capabilities.Framing, v.Capabilities.Media, v.Capabilities.Audio, v.Capabilities.SourceRateControl, fmt.Sprintf(\"%d\", len(v.Capabilities.ClientDecode))}", "\tfields = append(fields, v.Capabilities.ClientDecode...)", "\tvar transcript strings.Builder", "\ttranscript.WriteString(\"versevdi/tunnel-admission/v1\")", "\tfor _, field := range fields { fmt.Fprintf(&transcript, \"%d:%s\", len(field), field) }", "\treturn []byte(transcript.String())", "}", "", ]) # Use io.EOF in generated code without making every generated decoder depend on # error-string comparison; replace the deliberately compact placeholder. text = "\n".join(out).replace('"errors"\n"fmt"', '"errors"\n"fmt"\n\"io"') text = text.replace('!errors.Is(err, errors.New("EOF")) && err != nil', '!errors.Is(err, io.EOF)') return text + "\n" def rust_type(prop: dict[str, Any]) -> str: reference = ref_name(prop) if reference: return reference if prop.get("type") == "array": return f"Vec<{rust_type(prop.get('items', {}))}>" if prop.get("type") == "integer": return "i64" if prop.get("type") == "boolean": return "bool" if prop.get("type") == "object": return "JsonObject" return "String" def swift_type(prop: dict[str, Any]) -> str: reference = ref_name(prop) if reference: return reference if prop.get("type") == "array": return f"[{swift_type(prop.get('items', {}))}]" if prop.get("type") == "integer": return "Int64" if prop.get("type") == "boolean": return "Bool" if prop.get("type") == "object": return "JSONObject" return "String" def rust_validation(definition: dict[str, Any]) -> list[str]: lines: list[str] = [] required = set(definition.get("required", [])) for prop_name, prop in definition.get("properties", {}).items(): field = rust_field(prop_name) value = f"self.{field}" if prop_name not in required: value = f"value" lines.append(f" if let Some(value) = &self.{field} {{") prefix, suffix = " ", " }" else: prefix, suffix = "", "" if prop.get("type") == "string": if prop_name in required and prop.get("minLength", 0) > 0: lines.append(f" {prefix}if {value}.is_empty() {{ return Err(ValidationError::new(\"{prop_name}\", \"required\")); }}") if "minLength" in prop: lines.append(f" {prefix}if !{value}.is_empty() && {value}.len() < {prop['minLength']} {{ return Err(ValidationError::new(\"{prop_name}\", \"min_length\")); }}") if "maxLength" in prop: lines.append(f" {prefix}if {value}.len() > {prop['maxLength']} {{ return Err(ValidationError::new(\"{prop_name}\", \"max_length\")); }}") if "x-max-bytes" in prop: lines.append(f" {prefix}if {value}.as_bytes().len() > {prop['x-max-bytes']} {{ return Err(ValidationError::new(\"{prop_name}\", \"max_bytes\")); }}") if "const" in prop: lines.append(f" {prefix}if {value} != \"{prop['const']}\" {{ return Err(ValidationError::new(\"{prop_name}\", \"invalid_value\")); }}") if "enum" in prop: allowed = " && ".join(f'{value} != \"{item}\"' for item in prop["enum"]) lines.append(f" {prefix}if {allowed} {{ return Err(ValidationError::new(\"{prop_name}\", \"invalid_value\")); }}") if prop.get("format") == "base64url": lines.append(f" {prefix}if !valid_base64_url({value}.as_str()) {{ return Err(ValidationError::new(\"{prop_name}\", \"invalid_format\")); }}") if prop.get("type") == "integer": if "minimum" in prop: lines.append(f" {prefix}if {value} < {prop['minimum']} {{ return Err(ValidationError::new(\"{prop_name}\", \"minimum\")); }}") if "maximum" in prop: lines.append(f" {prefix}if {value} > {prop['maximum']} {{ return Err(ValidationError::new(\"{prop_name}\", \"maximum\")); }}") if prop.get("type") == "array": if "minItems" in prop: lines.append(f" {prefix}if {value}.len() < {prop['minItems']} {{ return Err(ValidationError::new(\"{prop_name}\", \"min_items\")); }}") if "maxItems" in prop: lines.append(f" {prefix}if {value}.len() > {prop['maxItems']} {{ return Err(ValidationError::new(\"{prop_name}\", \"max_items\")); }}") items = prop.get("items", {}) if "enum" in items: allowed = " && ".join(f'item != \"{item}\"' for item in items["enum"]) lines.append(f" {prefix}for item in {value}.iter() {{ if {allowed} {{ return Err(ValidationError::new(\"{prop_name}\", \"invalid_item\")); }} }}") if prop.get("uniqueItems") and items.get("type") == "string": lines.append(f" {prefix}for (index, item) in {value}.iter().enumerate() {{ if {value}[..index].contains(item) {{ return Err(ValidationError::new(\"{prop_name}\", \"duplicate_item\")); }} }}") item_ref = ref_name(items) if item_ref: lines.append(f" {prefix}for item in {value}.iter() {{ item.validate().map_err(|_| ValidationError::new(\"{prop_name}\", \"invalid_item\"))?; }}") reference = ref_name(prop) if reference: lines.append(f" {prefix}{value}.validate().map_err(|_| ValidationError::new(\"{prop_name}\", \"invalid_object\"))?;") if suffix: lines.append(suffix) name = definition["name"] if name in {"AllocationPolicy", "ManifestBounds"}: lines.append(" if self.minimumKbps > self.targetKbps || self.targetKbps > self.maximumKbps { return Err(ValidationError::new(\"bounds\", \"invalid_order\")); }") if name == "GatewayRegistration": lines.append(" if self.protocolMinVersion > self.protocolMaxVersion { return Err(ValidationError::new(\"protocol_version\", \"invalid_order\")); }") if name == "ChannelFrame": lines.append(" if self.fragmentIndex >= self.fragmentCount { return Err(ValidationError::new(\"fragment_index\", \"invalid_order\")); }") return lines def generate_rust(defs: dict[str, dict[str, Any]], schema_hash: str, compatibility: dict[str, Any]) -> str: out = [ "// Code generated by tools/generate.py; DO NOT EDIT.", "#![allow(non_snake_case)]", "pub const SCHEMA_SHA256: &str = \"" + schema_hash + "\";", f'pub const CURRENT_WIRE_VERSION: &str = "{compatibility["current"]}";', f'pub const N_MINUS_1_WIRE_VERSION: &str = "{compatibility["n_minus_1"]}";', f'pub const N_MINUS_2_WIRE_VERSION: &str = "{compatibility["n_minus_2"]}";', "pub type JsonObject = std::collections::BTreeMap;", "", "#[derive(Debug, Clone, PartialEq, Eq)]", "pub struct ValidationError { pub field: &'static str, pub code: &'static str }", "impl ValidationError { pub const fn new(field: &'static str, code: &'static str) -> Self { Self { field, code } } }", "", "fn base64url_value(value: u8) -> Option {", " match value {", " b'A'..=b'Z' => Some(value - b'A'),", " b'a'..=b'z' => Some(value - b'a' + 26),", " b'0'..=b'9' => Some(value - b'0' + 52),", " b'-' => Some(62),", " b'_' => Some(63),", " _ => None,", " }", "}", "fn valid_base64_url(value: &str) -> bool {", " let bytes = value.as_bytes();", " if bytes.is_empty() || bytes.iter().any(|byte| base64url_value(*byte).is_none()) { return false; }", " match bytes.len() % 4 {", " 0 => true,", " 2 => base64url_value(*bytes.last().unwrap()).unwrap() & 0x0f == 0,", " 3 => base64url_value(*bytes.last().unwrap()).unwrap() & 0x03 == 0,", " _ => false,", " }", "}", "", ] for name in sorted(defs): definition = defs[name] out.extend(["#[derive(Debug, Clone, PartialEq, Eq)]", f"pub struct {name} {{"]) required = set(definition.get("required", [])) for prop_name, prop in definition.get("properties", {}).items(): field = rust_field(prop_name) typ = rust_type(prop) if prop_name not in required: typ = f"Option<{typ}>" out.append(f" {field}: {typ},") out.extend(["}", ""]) parameters: list[str] = [] assignments: list[str] = [] for prop_name, prop in definition.get("properties", {}).items(): field = rust_field(prop_name) typ = rust_type(prop) if prop_name not in required: typ = f"Option<{typ}>" parameters.append(f"{field}: {typ}") assignments.append(field) out.append(f"impl {name} {{") out.append(f" pub fn new({', '.join(parameters)}) -> Result {{") out.append(f" let value = Self {{ {', '.join(assignments)} }};") out.append(" value.validate()?;") out.append(" Ok(value)") out.append(" }") out.append(" pub fn validate(&self) -> Result<(), ValidationError> {") out.extend(rust_validation(definition)) out.append(" Ok(())") out.append(" }") for prop_name, prop in definition.get("properties", {}).items(): field = rust_field(prop_name) typ = rust_type(prop) if prop_name not in required: typ = f"Option<{typ}>" out.append(f" pub fn {field}(&self) -> &{typ} {{ &self.{field} }}") if name == "TunnelAdmissionRequest": out.extend([ " pub fn device_admission_transcript(&self) -> Vec {", " let reconnect_sequence = self.reconnectSequence.to_string();", " let client_decode_count = self.capabilities.clientDecode.len().to_string();", " let mut fields = vec![self.sessionId.as_str(), self.gatewayId.as_str(), self.audience.as_str(), self.grant.as_str(), reconnect_sequence.as_str(), self.clientNonce.as_str(), self.capabilities.transport.as_str(), self.capabilities.framing.as_str(), self.capabilities.media.as_str(), self.capabilities.audio.as_str(), self.capabilities.sourceRateControl.as_str(), client_decode_count.as_str()];", " fields.extend(self.capabilities.clientDecode.iter().map(String::as_str));", " let mut transcript = String::from(\"versevdi/tunnel-admission/v1\");", " for field in fields { transcript.push_str(&format!(\"{}:{}\", field.as_bytes().len(), field)); }", " transcript.into_bytes()", " }", ]) out.extend(["}", ""]) out.extend([ "pub fn intersect_capability_profiles(profiles: &[CapabilityProfile]) -> Result {", " let mut selected = profiles.first().ok_or_else(|| ValidationError::new(\"capabilities\", \"no_overlap\"))?.clone();", " selected.validate().map_err(|_| ValidationError::new(\"capabilities\", \"no_overlap\"))?;", " for profile in &profiles[1..] {", " profile.validate().map_err(|_| ValidationError::new(\"capabilities\", \"no_overlap\"))?;", " if profile.transport != selected.transport || profile.framing != selected.framing || profile.media != selected.media || profile.audio != selected.audio || profile.sourceRateControl != selected.sourceRateControl { return Err(ValidationError::new(\"capabilities\", \"no_overlap\")); }", " selected.clientDecode.retain(|candidate| profile.clientDecode.contains(candidate));", " if selected.clientDecode.is_empty() { return Err(ValidationError::new(\"capabilities\", \"no_overlap\")); }", " }", " Ok(selected)", "}", "", ]) return "\n".join(out) def swift_validation(definition: dict[str, Any]) -> list[str]: lines: list[str] = [] required = set(definition.get("required", [])) for prop_name, prop in definition.get("properties", {}).items(): field = swift_field(prop_name) value = f"self.{field}" if prop_name not in required: value = "value" lines.append(f" if let value = self.{field} {{") prefix, suffix = " ", " }" else: prefix, suffix = "", "" if prop.get("type") == "string": if prop_name in required and prop.get("minLength", 0) > 0: lines.append(f" {prefix}if {value}.isEmpty {{ throw ContractValidationError(field: \"{prop_name}\", code: \"required\") }}") if "minLength" in prop: lines.append(f" {prefix}if !{value}.isEmpty && {value}.utf8.count < {prop['minLength']} {{ throw ContractValidationError(field: \"{prop_name}\", code: \"min_length\") }}") if "maxLength" in prop: lines.append(f" {prefix}if {value}.utf8.count > {prop['maxLength']} {{ throw ContractValidationError(field: \"{prop_name}\", code: \"max_length\") }}") if "x-max-bytes" in prop: lines.append(f" {prefix}if {value}.utf8.count > {prop['x-max-bytes']} {{ throw ContractValidationError(field: \"{prop_name}\", code: \"max_bytes\") }}") if "const" in prop: lines.append(f" {prefix}if {value} != \"{prop['const']}\" {{ throw ContractValidationError(field: \"{prop_name}\", code: \"invalid_value\") }}") if "enum" in prop: allowed = ", ".join(f'\"{item}\"' for item in prop["enum"]) lines.append(f" {prefix}if ![{allowed}].contains({value}) {{ throw ContractValidationError(field: \"{prop_name}\", code: \"invalid_value\") }}") if prop.get("format") == "date-time": lines.append(f" {prefix}if ISO8601DateFormatter().date(from: {value}) == nil {{ throw ContractValidationError(field: \"{prop_name}\", code: \"invalid_time\") }}") if prop.get("format") == "base64url": lines.append(f" {prefix}if !validBase64URL({value}) {{ throw ContractValidationError(field: \"{prop_name}\", code: \"invalid_format\") }}") if prop.get("type") == "integer": if "minimum" in prop: lines.append(f" {prefix}if {value} < {prop['minimum']} {{ throw ContractValidationError(field: \"{prop_name}\", code: \"minimum\") }}") if "maximum" in prop: lines.append(f" {prefix}if {value} > {prop['maximum']} {{ throw ContractValidationError(field: \"{prop_name}\", code: \"maximum\") }}") if prop.get("type") == "array": if "minItems" in prop: lines.append(f" {prefix}if {value}.count < {prop['minItems']} {{ throw ContractValidationError(field: \"{prop_name}\", code: \"min_items\") }}") if "maxItems" in prop: lines.append(f" {prefix}if {value}.count > {prop['maxItems']} {{ throw ContractValidationError(field: \"{prop_name}\", code: \"max_items\") }}") items = prop.get("items", {}) if "enum" in items: allowed = ", ".join(f'\"{item}\"' for item in items["enum"]) lines.append(f" {prefix}for item in {value} where ![{allowed}].contains(item) {{ throw ContractValidationError(field: \"{prop_name}\", code: \"invalid_item\") }}") if prop.get("uniqueItems") and items.get("type") == "string": lines.append(f" {prefix}if Set({value}).count != {value}.count {{ throw ContractValidationError(field: \"{prop_name}\", code: \"duplicate_item\") }}") item_ref = ref_name(items) if item_ref: lines.append(f" {prefix}for item in {value} {{ try item.validate() }}") reference = ref_name(prop) if reference: lines.append(f" {prefix}try {value}.validate()") if suffix: lines.append(suffix) name = definition["name"] if name in {"AllocationPolicy", "ManifestBounds"}: lines.append(" if minimumKbps > targetKbps || targetKbps > maximumKbps { throw ContractValidationError(field: \"bounds\", code: \"invalid_order\") }") if name == "GatewayRegistration": lines.append(" if protocolMinVersion > protocolMaxVersion { throw ContractValidationError(field: \"protocol_version\", code: \"invalid_order\") }") if name == "ChannelFrame": lines.append(" if fragmentIndex >= fragmentCount { throw ContractValidationError(field: \"fragment_index\", code: \"invalid_order\") }") return lines def generate_swift(defs: dict[str, dict[str, Any]], schema_hash: str, compatibility: dict[str, Any]) -> str: out = [ "// Code generated by tools/generate.py; DO NOT EDIT.", "import Foundation", "public typealias JSONObject = [String: String]", f"public let schemaSHA256 = \"{schema_hash}\"", f'public let currentWireVersion = "{compatibility["current"]}"', f'public let nMinus1WireVersion = "{compatibility["n_minus_1"]}"', f'public let nMinus2WireVersion = "{compatibility["n_minus_2"]}"', "public struct ContractValidationError: Error, Equatable { public let field: String; public let code: String }", "private struct AnyCodingKey: CodingKey { let stringValue: String; let intValue: Int?; init?(stringValue: String) { self.stringValue = stringValue; self.intValue = nil }; init?(intValue: Int) { self.stringValue = String(intValue); self.intValue = intValue } }", "private func validBase64URL(_ value: String) -> Bool {", " guard !value.isEmpty, value.utf8.allSatisfy({ byte in", " (byte >= 65 && byte <= 90) || (byte >= 97 && byte <= 122) || (byte >= 48 && byte <= 57) || byte == 45 || byte == 95", " }) else { return false }", " let padding = String(repeating: \"=\", count: (4 - value.utf8.count % 4) % 4)", " let standard = value.replacingOccurrences(of: \"-\", with: \"+\").replacingOccurrences(of: \"_\", with: \"/\") + padding", " guard let decoded = Data(base64Encoded: standard) else { return false }", " return decoded.base64EncodedString().replacingOccurrences(of: \"+\", with: \"-\").replacingOccurrences(of: \"/\", with: \"_\").replacingOccurrences(of: \"=\", with: \"\") == value", "}", "", ] for name in sorted(defs): definition = defs[name] required = set(definition.get("required", [])) out.extend(["public struct " + name + ": Codable, Equatable {",]) for prop_name, prop in definition.get("properties", {}).items(): typ = swift_type(prop) if prop_name not in required: typ += "?" out.append(f" public let {swift_field(prop_name)}: {typ}") out.append(" enum CodingKeys: String, CodingKey {") for prop_name in definition.get("properties", {}): out.append(f" case {swift_field(prop_name)} = \"{prop_name}\"") parameters: list[str] = [] for prop_name, prop in definition.get("properties", {}).items(): typ = swift_type(prop) if prop_name not in required: typ += "?" parameters.append(f"{swift_field(prop_name)}: {typ}") out.extend([" }", "", f" public init({', '.join(parameters)}) throws {{"]) for prop_name in definition.get("properties", {}): field = swift_field(prop_name) out.append(f" self.{field} = {field}") out.extend([" try validate()", " }", "", " public init(from decoder: Decoder) throws {"]) out.append(" let all = try decoder.container(keyedBy: AnyCodingKey.self)") out.append(" for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: \"unknown_field\") }") out.append(" let c = try decoder.container(keyedBy: CodingKeys.self)") decoded: list[str] = [] for prop_name, prop in definition.get("properties", {}).items(): field = swift_field(prop_name) typ = swift_type(prop) if prop_name in required: decoded.append(f"{field}: try c.decode({typ}.self, forKey: .{field})") elif ref_name(prop): decoded.append(f"{field}: try c.contains(.{field}) ? c.decode({typ}.self, forKey: .{field}) : nil") else: decoded.append(f"{field}: try c.decodeIfPresent({typ}.self, forKey: .{field})") out.append(f" try self.init({', '.join(decoded)})") out.extend([" }", "", " public func validate() throws {"]) out.extend(swift_validation(definition)) out.extend([" }", "", " public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }", " public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }", "}", ""]) out.extend([ "public extension TunnelAdmissionRequest {", " func deviceAdmissionTranscript() -> Data {", " var fields = [sessionId, gatewayId, audience, grant, String(reconnectSequence), clientNonce, capabilities.transport, capabilities.framing, capabilities.media, capabilities.audio, capabilities.sourceRateControl, String(capabilities.clientDecode.count)]", " fields.append(contentsOf: capabilities.clientDecode)", " var transcript = \"versevdi/tunnel-admission/v1\"", " for field in fields { transcript += \"\\(field.utf8.count):\\(field)\" }", " return Data(transcript.utf8)", " }", "}", "", "public extension CapabilityProfile {", " static func intersection(_ profiles: [CapabilityProfile]) throws -> CapabilityProfile {", " guard let selected = profiles.first else { throw ContractValidationError(field: \"capabilities\", code: \"no_overlap\") }", " try selected.validate()", " var common = selected.clientDecode", " for profile in profiles.dropFirst() {", " try profile.validate()", " if profile.transport != selected.transport || profile.framing != selected.framing || profile.media != selected.media || profile.audio != selected.audio || profile.sourceRateControl != selected.sourceRateControl { throw ContractValidationError(field: \"capabilities\", code: \"no_overlap\") }", " common = common.filter { profile.clientDecode.contains($0) }", " if common.isEmpty { throw ContractValidationError(field: \"capabilities\", code: \"no_overlap\") }", " }", " return try CapabilityProfile(transport: selected.transport, framing: selected.framing, media: selected.media, audio: selected.audio, sourceRateControl: selected.sourceRateControl, clientDecode: common)", " }", "}", "", ]) return "\n".join(out) def write_or_check(path: pathlib.Path, content: str, check: bool) -> None: if check: if not path.exists() or path.read_text(encoding="utf-8") != content: raise ValueError(f"generated output differs: {path.relative_to(ROOT)}") return path.parent.mkdir(parents=True, exist_ok=True) path.write_text(content, encoding="utf-8") def format_go(content: str) -> str: result = subprocess.run(["gofmt"], input=content, text=True, capture_output=True, check=False) if result.returncode != 0: raise ValueError(f"gofmt failed: {result.stderr.strip()}") return result.stdout def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--check", action="store_true") args = parser.parse_args() schema, schema_hash, version, compatibility = load_schema() generator_hash = hashlib.sha256(pathlib.Path(__file__).read_bytes()).hexdigest() defs = schema.get("$defs") if not isinstance(defs, dict) or not defs: raise ValueError("schema must contain non-empty $defs") normalized = {name: dict(value, name=name) for name, value in defs.items()} outputs = { ROOT / "gen/go/protocol/protocol.go": format_go(generate_go(normalized, schema_hash, version, compatibility)), ROOT / "gen/rust/protocol.rs": generate_rust(normalized, schema_hash, compatibility).rstrip() + "\n", ROOT / "gen/swift/Protocol.swift": generate_swift(normalized, schema_hash, compatibility).rstrip() + "\n", ROOT / "gen/manifest.json": json.dumps({ "generator_sha256": generator_hash, "schema_sha256": schema_hash, "protocol_version": version, "compatibility": compatibility, }, sort_keys=True, indent=2) + "\n", } for path, content in outputs.items(): write_or_check(path, content, args.check) return 0 if __name__ == "__main__": try: raise SystemExit(main()) except (OSError, ValueError, json.JSONDecodeError) as exc: print(f"generate: {exc}", file=sys.stderr) raise SystemExit(1)