Files
VerseVDI-Protocol/tools/generate.py
T

968 lines
67 KiB
Python

#!/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("format") == "uuid":
lines.append(f"\tif v.{field} != \"\" && !validCanonicalUUID(v.{field}) {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"invalid_uuid\"}}) }}")
if prop.get("type") == "integer":
value = f"*v.{field}" if prop.get("x-optional-pointer") else f"v.{field}"
guard = f"v.{field} != nil && " if prop.get("x-optional-pointer") else ""
if "minimum" in prop:
lines.append(f"\tif {guard}{value} != 0 && {value} < {prop['minimum']} {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"minimum\"}}) }}")
if "maximum" in prop:
lines.append(f"\tif {guard}{value} > {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 items.get("type") == "string" and "minLength" in items:
lines.append(f"\tfor _, item := range v.{field} {{ if len(item) < {items['minLength']} {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"min_item_length\"}}) }} }}")
if items.get("type") == "string" and "maxLength" in items:
lines.append(f"\tfor _, item := range v.{field} {{ if len(item) > {items['maxLength']} {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"max_item_length\"}}) }} }}")
if items.get("type") == "string" and "x-max-bytes" in items:
lines.append(f"\tfor _, item := range v.{field} {{ if len(item) > {items['x-max-bytes']} {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"max_item_bytes\"}}) }} }}")
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"):
lines.append(f"\tfor index, item := range v.{field} {{ for prior := 0; prior < index; prior++ {{ if reflect.DeepEqual(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 == "SessionQualityLimits":
lines.append("\tif v.BitrateMinimumKbps > v.BitrateTargetKbps || v.BitrateTargetKbps > v.BitrateMaximumKbps { violations = append(violations, FieldViolation{Field: \"bitrate_bounds\", Code: \"invalid_order\"}) }")
if name in {"SelectedSessionDescriptor", "ProviderStreamPolicy"}:
lines.append("\tif v.BitrateTargetKbps > v.BitrateMaximumKbps { violations = append(violations, FieldViolation{Field: \"bitrate_bounds\", Code: \"invalid_order\"}) }")
if name == "BitratePreference":
lines.append("\tif v.Mode == \"auto\" && v.TargetKbps != nil || v.Mode == \"explicit\" && v.TargetKbps == nil { violations = append(violations, FieldViolation{Field: \"target_kbps\", Code: \"invalid_tagged_value\"}) }")
if name == "GatewayQualityWorkRequest":
lines.append("\tif v.Acquisition == \"poll\" && (v.OperationID != \"\" || v.Revision != nil || v.LeaseGeneration != nil || v.CurrentAppliedRevision != nil) { violations = append(violations, FieldViolation{Field: \"acquisition\", Code: \"invalid_tagged_value\"}) }")
lines.append("\tif v.Acquisition == \"prompt\" && (v.OperationID == \"\" || v.Revision == nil || v.LeaseGeneration != nil || v.CurrentAppliedRevision != nil) { violations = append(violations, FieldViolation{Field: \"acquisition\", Code: \"invalid_tagged_value\"}) }")
lines.append("\tif v.Acquisition == \"observation\" && (v.OperationID == \"\" || v.Revision == nil || v.LeaseGeneration == nil || v.CurrentAppliedRevision == nil) { violations = append(violations, FieldViolation{Field: \"acquisition\", Code: \"invalid_tagged_value\"}) }")
if name == "GatewayStopWorkRequest":
lines.append("\tif v.Acquisition == \"poll\" && v.OperationID != \"\" || v.Acquisition == \"prompt\" && v.OperationID == \"\" { violations = append(violations, FieldViolation{Field: \"acquisition\", Code: \"invalid_tagged_value\"}) }")
if name == "GatewayQualityAck":
lines.append("\tif v.Outcome == \"applied\" && (v.CurrentAppliedRevision == nil || *v.CurrentAppliedRevision != v.Revision) { violations = append(violations, FieldViolation{Field: \"current_applied_revision\", Code: \"invalid_tagged_value\"}) }")
lines.append("\tif v.Outcome == \"proven_prior\" && (v.CurrentAppliedRevision == nil || *v.CurrentAppliedRevision >= v.Revision) { violations = append(violations, FieldViolation{Field: \"current_applied_revision\", Code: \"invalid_tagged_value\"}) }")
lines.append("\tif v.Outcome == \"unknown\" && v.CurrentAppliedRevision != nil { violations = append(violations, FieldViolation{Field: \"current_applied_revision\", Code: \"invalid_tagged_value\"}) }")
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/binary\"",
"\"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\" }",
"",
"func validCanonicalUUID(value string) bool {",
"\tif len(value) != 36 || value[8] != '-' || value[13] != '-' || value[18] != '-' || value[23] != '-' { return false }",
"\tfor index, char := range []byte(value) { if index == 8 || index == 13 || index == 18 || index == 23 { continue }; if !((char >= '0' && char <= '9') || (char >= 'a' && char <= 'f')) { return false } }",
"\treturn value != \"00000000-0000-0000-0000-000000000000\"",
"}",
"",
"func rejectDuplicateJSONKeys(data []byte) error {",
"\tdecoder := json.NewDecoder(bytes.NewReader(data))",
"\tvar scan func(json.Token) error",
"\tscan = func(token json.Token) error {",
"\t\tdelim, ok := token.(json.Delim); if !ok { return nil }",
"\t\tswitch delim {",
"\t\tcase '{':",
"\t\t\tseen := map[string]struct{}{}",
"\t\t\tfor decoder.More() { keyToken, err := decoder.Token(); if err != nil { return err }; key, ok := keyToken.(string); if !ok { return errors.New(\"invalid JSON object key\") }; if _, exists := seen[key]; exists { return errors.New(\"duplicate JSON object key\") }; seen[key] = struct{}{}; value, err := decoder.Token(); if err != nil { return err }; if err := scan(value); err != nil { return err } }",
"\t\t\t_, err := decoder.Token(); return err",
"\t\tcase '[':",
"\t\t\tfor decoder.More() { value, err := decoder.Token(); if err != nil { return err }; if err := scan(value); err != nil { return err } }; _, err := decoder.Token(); return err",
"\t\t}",
"\t\treturn nil",
"\t}",
"\ttoken, err := decoder.Token(); if err != nil { return err }; return scan(token)",
"}",
"",
]
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.get("x-optional-pointer"):
typ = "*" + typ
elif 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("\tif err := rejectDuplicateJSONKeys(data); err != nil { return value, err }")
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([
"func DeviceRegistrationProofTranscript(serverID, principalID, deviceID, challenge []byte, expiryUnixMilliseconds int64) ([]byte, error) {",
"\tfor _, value := range []struct { field string; bytes []byte; length int }{{\"server_id\", serverID, 16}, {\"principal_id\", principalID, 16}, {\"device_id\", deviceID, 16}, {\"challenge\", challenge, 32}} {",
"\t\tif len(value.bytes) != value.length { return nil, ValidationError{Violations: []FieldViolation{{Field: value.field, Code: \"invalid_length\"}}} }",
"\t}",
"\tif expiryUnixMilliseconds < 0 { return nil, ValidationError{Violations: []FieldViolation{{Field: \"expiry_unix_milliseconds\", Code: \"minimum\"}}} }",
"\ttranscript := make([]byte, 0, 112)",
"\ttranscript = append(transcript, \"versevdi-device-proof-v1\"...)",
"\ttranscript = append(transcript, serverID...)",
"\ttranscript = append(transcript, principalID...)",
"\ttranscript = append(transcript, deviceID...)",
"\ttranscript = append(transcript, challenge...)",
"\tvar expiry [8]byte",
"\tbinary.BigEndian.PutUint64(expiry[:], uint64(expiryUnixMilliseconds))",
"\treturn append(transcript, expiry[:]...), nil",
"}",
"",
"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 }",
"\tcommonVideo := append([]VideoProfile(nil), selected.VideoProfiles...)",
"\tcommonAudio := append([]AudioProfile(nil), selected.AudioProfiles...)",
"\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.SourceRateControl != selected.SourceRateControl { return CapabilityProfile{}, ErrNoCapabilityOverlap }",
"\t\tnextVideo := commonVideo[:0]",
"\t\tfor _, candidate := range commonVideo { for _, offered := range profile.VideoProfiles { if candidate == offered { nextVideo = append(nextVideo, candidate); break } } }",
"\t\tcommonVideo = nextVideo",
"\t\tnextAudio := commonAudio[:0]",
"\t\tfor _, candidate := range commonAudio { for _, offered := range profile.AudioProfiles { if candidate == offered { nextAudio = append(nextAudio, candidate); break } } }",
"\t\tcommonAudio = nextAudio",
"\t\tif len(commonVideo) == 0 || len(commonAudio) == 0 { return CapabilityProfile{}, ErrNoCapabilityOverlap }",
"\t}",
"\tselected.VideoProfiles = commonVideo",
"\tselected.AudioProfiles = commonAudio",
"\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.SourceRateControl, fmt.Sprintf(\"%d\", len(v.Capabilities.VideoProfiles)), fmt.Sprintf(\"%d\", len(v.Capabilities.AudioProfiles))}",
"\tfor _, profile := range v.Capabilities.VideoProfiles { fields = append(fields, profile.Codec, fmt.Sprintf(\"%d\", profile.BitDepth), profile.ChromaSubsampling, profile.ColorSpace, profile.TransferFunction) }",
"\tfor _, profile := range v.Capabilities.AudioProfiles { fields = append(fields, profile.Codec, fmt.Sprintf(\"%d\", profile.SampleRateHz), fmt.Sprintf(\"%d\", profile.Channels), profile.ChannelLayout, fmt.Sprintf(\"%d\", profile.PacketDurationMs)) }",
"\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") == "date-time":
lines.append(f" {prefix}if !valid_rfc3339_utc({value}.as_str()) {{ return Err(ValidationError::new(\"{prop_name}\", \"invalid_time\")); }}")
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("format") == "uuid":
lines.append(f" {prefix}if !valid_canonical_uuid({value}.as_str()) {{ return Err(ValidationError::new(\"{prop_name}\", \"invalid_uuid\")); }}")
if prop.get("type") == "integer":
numeric = f"*{value}" if prop_name not in required else value
if "minimum" in prop:
lines.append(f" {prefix}if {numeric} < {prop['minimum']} {{ return Err(ValidationError::new(\"{prop_name}\", \"minimum\")); }}")
if "maximum" in prop:
lines.append(f" {prefix}if {numeric} > {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 items.get("type") == "string" and "minLength" in items:
lines.append(f" {prefix}for item in {value}.iter() {{ if item.as_bytes().len() < {items['minLength']} {{ return Err(ValidationError::new(\"{prop_name}\", \"min_item_length\")); }} }}")
if items.get("type") == "string" and "maxLength" in items:
lines.append(f" {prefix}for item in {value}.iter() {{ if item.as_bytes().len() > {items['maxLength']} {{ return Err(ValidationError::new(\"{prop_name}\", \"max_item_length\")); }} }}")
if items.get("type") == "string" and "x-max-bytes" in items:
lines.append(f" {prefix}for item in {value}.iter() {{ if item.as_bytes().len() > {items['x-max-bytes']} {{ return Err(ValidationError::new(\"{prop_name}\", \"max_item_bytes\")); }} }}")
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"):
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 == "SessionQualityLimits":
lines.append(" if self.bitrateMinimumKbps > self.bitrateTargetKbps || self.bitrateTargetKbps > self.bitrateMaximumKbps { return Err(ValidationError::new(\"bitrate_bounds\", \"invalid_order\")); }")
if name in {"SelectedSessionDescriptor", "ProviderStreamPolicy"}:
lines.append(" if self.bitrateTargetKbps > self.bitrateMaximumKbps { return Err(ValidationError::new(\"bitrate_bounds\", \"invalid_order\")); }")
if name == "BitratePreference":
lines.append(" if self.mode == \"auto\" && self.targetKbps.is_some() || self.mode == \"explicit\" && self.targetKbps.is_none() { return Err(ValidationError::new(\"target_kbps\", \"invalid_tagged_value\")); }")
if name == "GatewayQualityWorkRequest":
lines.append(" if self.acquisition == \"poll\" && (self.operationId.is_some() || self.revision.is_some() || self.leaseGeneration.is_some() || self.currentAppliedRevision.is_some()) { return Err(ValidationError::new(\"acquisition\", \"invalid_tagged_value\")); }")
lines.append(" if self.acquisition == \"prompt\" && (self.operationId.is_none() || self.revision.is_none() || self.leaseGeneration.is_some() || self.currentAppliedRevision.is_some()) { return Err(ValidationError::new(\"acquisition\", \"invalid_tagged_value\")); }")
lines.append(" if self.acquisition == \"observation\" && (self.operationId.is_none() || self.revision.is_none() || self.leaseGeneration.is_none() || self.currentAppliedRevision.is_none()) { return Err(ValidationError::new(\"acquisition\", \"invalid_tagged_value\")); }")
if name == "GatewayStopWorkRequest":
lines.append(" if self.acquisition == \"poll\" && self.operationId.is_some() || self.acquisition == \"prompt\" && self.operationId.is_none() { return Err(ValidationError::new(\"acquisition\", \"invalid_tagged_value\")); }")
if name == "GatewayQualityAck":
lines.append(" if self.outcome == \"applied\" && self.currentAppliedRevision != Some(self.revision) { return Err(ValidationError::new(\"current_applied_revision\", \"invalid_tagged_value\")); }")
lines.append(" if self.outcome == \"proven_prior\" && self.currentAppliedRevision.map_or(true, |current| current >= self.revision) { return Err(ValidationError::new(\"current_applied_revision\", \"invalid_tagged_value\")); }")
lines.append(" if self.outcome == \"unknown\" && self.currentAppliedRevision.is_some() { return Err(ValidationError::new(\"current_applied_revision\", \"invalid_tagged_value\")); }")
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<String, String>;",
"",
"#[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<u8> {",
" 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,",
" }",
"}",
"fn valid_rfc3339_utc(value: &str) -> bool {",
" let bytes = value.as_bytes();",
" if bytes.len() < 20 || bytes.len() > 30 || bytes[4] != b'-' || bytes[7] != b'-' || bytes[10] != b'T' || bytes[13] != b':' || bytes[16] != b':' || *bytes.last().unwrap() != b'Z' { return false; }",
" let digits = |start: usize, end: usize| -> Option<u32> { bytes.get(start..end)?.iter().try_fold(0u32, |value, byte| if byte.is_ascii_digit() { Some(value * 10 + u32::from(*byte - b'0')) } else { None }) };",
" let (year, month, day, hour, minute, second) = match (digits(0, 4), digits(5, 7), digits(8, 10), digits(11, 13), digits(14, 16), digits(17, 19)) { (Some(year), Some(month), Some(day), Some(hour), Some(minute), Some(second)) => (year, month, day, hour, minute, second), _ => return false };",
" if hour > 23 || minute > 59 || second > 59 { return false; }",
" let leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);",
" let days = match month { 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, 4 | 6 | 9 | 11 => 30, 2 if leap => 29, 2 => 28, _ => return false };",
" if day == 0 || day > days { return false; }",
" if bytes.len() == 20 { return true; }",
" let fraction = &bytes[20..bytes.len() - 1];",
" bytes[19] == b'.' && !fraction.is_empty() && fraction.len() <= 9 && fraction.iter().all(u8::is_ascii_digit) && *fraction.last().unwrap() != b'0'",
"}",
"fn valid_canonical_uuid(value: &str) -> bool {",
" let bytes = value.as_bytes();",
" bytes.len() == 36 && [8, 13, 18, 23].iter().all(|index| bytes[*index] == b'-') && bytes.iter().enumerate().all(|(index, byte)| [8, 13, 18, 23].contains(&index) || byte.is_ascii_digit() || (b'a'..=b'f').contains(byte)) && value != \"00000000-0000-0000-0000-000000000000\"",
"}",
"",
]
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<Self, ValidationError> {{")
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<u8> {",
" let reconnect_sequence = self.reconnectSequence.to_string();",
" let video_count = self.capabilities.videoProfiles.len().to_string();",
" let audio_count = self.capabilities.audioProfiles.len().to_string();",
" let mut owned = vec![self.sessionId.clone(), self.gatewayId.clone(), self.audience.clone(), self.grant.clone(), reconnect_sequence, self.clientNonce.clone(), self.capabilities.transport.clone(), self.capabilities.framing.clone(), self.capabilities.media.clone(), self.capabilities.sourceRateControl.clone(), video_count, audio_count];",
" for profile in &self.capabilities.videoProfiles { owned.extend([profile.codec.clone(), profile.bitDepth.to_string(), profile.chromaSubsampling.clone(), profile.colorSpace.clone(), profile.transferFunction.clone()]); }",
" for profile in &self.capabilities.audioProfiles { owned.extend([profile.codec.clone(), profile.sampleRateHz.to_string(), profile.channels.to_string(), profile.channelLayout.clone(), profile.packetDurationMs.to_string()]); }",
" let fields: Vec<&str> = owned.iter().map(String::as_str).collect();",
" 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 device_registration_proof_transcript(server_id: &[u8], principal_id: &[u8], device_id: &[u8], challenge: &[u8], expiry_unix_milliseconds: i64) -> Result<Vec<u8>, ValidationError> {",
" for (field, value, length) in [(\"server_id\", server_id, 16), (\"principal_id\", principal_id, 16), (\"device_id\", device_id, 16), (\"challenge\", challenge, 32)] {",
" if value.len() != length { return Err(ValidationError::new(field, \"invalid_length\")); }",
" }",
" if expiry_unix_milliseconds < 0 { return Err(ValidationError::new(\"expiry_unix_milliseconds\", \"minimum\")); }",
" let mut transcript = Vec::with_capacity(112);",
" transcript.extend_from_slice(b\"versevdi-device-proof-v1\");",
" transcript.extend_from_slice(server_id);",
" transcript.extend_from_slice(principal_id);",
" transcript.extend_from_slice(device_id);",
" transcript.extend_from_slice(challenge);",
" transcript.extend_from_slice(&(expiry_unix_milliseconds as u64).to_be_bytes());",
" Ok(transcript)",
"}",
"",
"pub fn intersect_capability_profiles(profiles: &[CapabilityProfile]) -> Result<CapabilityProfile, ValidationError> {",
" 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.sourceRateControl != selected.sourceRateControl { return Err(ValidationError::new(\"capabilities\", \"no_overlap\")); }",
" selected.videoProfiles.retain(|candidate| profile.videoProfiles.contains(candidate));",
" selected.audioProfiles.retain(|candidate| profile.audioProfiles.contains(candidate));",
" if selected.videoProfiles.is_empty() || selected.audioProfiles.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 !validRFC3339UTC({value}) {{ 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("format") == "uuid":
lines.append(f" {prefix}if !validCanonicalUUID({value}) {{ throw ContractValidationError(field: \"{prop_name}\", code: \"invalid_uuid\") }}")
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 items.get("type") == "string" and "minLength" in items:
lines.append(f" {prefix}for item in {value} where item.utf8.count < {items['minLength']} {{ throw ContractValidationError(field: \"{prop_name}\", code: \"min_item_length\") }}")
if items.get("type") == "string" and "maxLength" in items:
lines.append(f" {prefix}for item in {value} where item.utf8.count > {items['maxLength']} {{ throw ContractValidationError(field: \"{prop_name}\", code: \"max_item_length\") }}")
if items.get("type") == "string" and "x-max-bytes" in items:
lines.append(f" {prefix}for item in {value} where item.utf8.count > {items['x-max-bytes']} {{ throw ContractValidationError(field: \"{prop_name}\", code: \"max_item_bytes\") }}")
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"):
lines.append(f" {prefix}for (index, item) in {value}.enumerated() where {value}[..<index].contains(item) {{ 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 == "SessionQualityLimits":
lines.append(" if bitrateMinimumKbps > bitrateTargetKbps || bitrateTargetKbps > bitrateMaximumKbps { throw ContractValidationError(field: \"bitrate_bounds\", code: \"invalid_order\") }")
if name in {"SelectedSessionDescriptor", "ProviderStreamPolicy"}:
lines.append(" if bitrateTargetKbps > bitrateMaximumKbps { throw ContractValidationError(field: \"bitrate_bounds\", code: \"invalid_order\") }")
if name == "BitratePreference":
lines.append(" if mode == \"auto\" && targetKbps != nil || mode == \"explicit\" && targetKbps == nil { throw ContractValidationError(field: \"target_kbps\", code: \"invalid_tagged_value\") }")
if name == "GatewayQualityWorkRequest":
lines.append(" if acquisition == \"poll\" && (operationId != nil || revision != nil || leaseGeneration != nil || currentAppliedRevision != nil) { throw ContractValidationError(field: \"acquisition\", code: \"invalid_tagged_value\") }")
lines.append(" if acquisition == \"prompt\" && (operationId == nil || revision == nil || leaseGeneration != nil || currentAppliedRevision != nil) { throw ContractValidationError(field: \"acquisition\", code: \"invalid_tagged_value\") }")
lines.append(" if acquisition == \"observation\" && (operationId == nil || revision == nil || leaseGeneration == nil || currentAppliedRevision == nil) { throw ContractValidationError(field: \"acquisition\", code: \"invalid_tagged_value\") }")
if name == "GatewayStopWorkRequest":
lines.append(" if acquisition == \"poll\" && operationId != nil || acquisition == \"prompt\" && operationId == nil { throw ContractValidationError(field: \"acquisition\", code: \"invalid_tagged_value\") }")
if name == "GatewayQualityAck":
lines.append(" if outcome == \"applied\" && currentAppliedRevision != revision { throw ContractValidationError(field: \"current_applied_revision\", code: \"invalid_tagged_value\") }")
lines.append(" if outcome == \"proven_prior\" && (currentAppliedRevision == nil || currentAppliedRevision! >= revision) { throw ContractValidationError(field: \"current_applied_revision\", code: \"invalid_tagged_value\") }")
lines.append(" if outcome == \"unknown\" && currentAppliedRevision != nil { throw ContractValidationError(field: \"current_applied_revision\", code: \"invalid_tagged_value\") }")
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 rejectDuplicateJSONKeys(_ data: Data) throws {",
" guard data.count <= 1_048_576 else { throw ContractValidationError(field: \"json\", code: \"payload_too_large\") }",
" var index = 0",
" func skipWhitespace() { while index < data.count && [9, 10, 13, 32].contains(data[index]) { index += 1 } }",
" func parseString() throws -> String {",
" guard index < data.count, data[index] == 34 else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
" let start = index",
" index += 1",
" while index < data.count {",
" if data[index] == 92 { index += 2; continue }",
" if data[index] == 34 { index += 1; return try JSONDecoder().decode(String.self, from: data[start..<index]) }",
" index += 1",
" }",
" throw ContractValidationError(field: \"json\", code: \"invalid_json\")",
" }",
" func parseValue(_ depth: Int) throws {",
" guard depth <= 64 else { throw ContractValidationError(field: \"json\", code: \"nesting_too_deep\") }",
" skipWhitespace()",
" guard index < data.count else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
" if data[index] == 123 {",
" index += 1",
" var keys = Set<String>()",
" skipWhitespace()",
" if index < data.count, data[index] == 125 { index += 1; return }",
" while true {",
" skipWhitespace()",
" let key = try parseString()",
" guard keys.insert(key).inserted else { throw ContractValidationError(field: key, code: \"duplicate_field\") }",
" skipWhitespace()",
" guard index < data.count, data[index] == 58 else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
" index += 1",
" try parseValue(depth + 1)",
" skipWhitespace()",
" guard index < data.count else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
" if data[index] == 125 { index += 1; return }",
" guard data[index] == 44 else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
" index += 1",
" }",
" }",
" if data[index] == 91 {",
" index += 1",
" skipWhitespace()",
" if index < data.count, data[index] == 93 { index += 1; return }",
" while true {",
" try parseValue(depth + 1)",
" skipWhitespace()",
" guard index < data.count else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
" if data[index] == 93 { index += 1; return }",
" guard data[index] == 44 else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
" index += 1",
" }",
" }",
" if data[index] == 34 { _ = try parseString(); return }",
" let start = index",
" while index < data.count && ![9, 10, 13, 32, 44, 93, 125].contains(data[index]) { index += 1 }",
" guard index > start else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
" }",
" try parseValue(0)",
" skipWhitespace()",
" guard index == data.count else { throw ContractValidationError(field: \"json\", code: \"trailing_json\") }",
"}",
"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",
"}",
"private func validRFC3339UTC(_ value: String) -> Bool {",
" let bytes = Array(value.utf8)",
" guard (20...30).contains(bytes.count), bytes[4] == 45, bytes[7] == 45, bytes[10] == 84, bytes[13] == 58, bytes[16] == 58, bytes.last == 90 else { return false }",
" func digits(_ range: Range<Int>) -> Int? {",
" var result = 0",
" for index in range { guard bytes[index] >= 48 && bytes[index] <= 57 else { return nil }; result = result * 10 + Int(bytes[index] - 48) }",
" return result",
" }",
" guard let year = digits(0..<4), let month = digits(5..<7), let day = digits(8..<10), let hour = digits(11..<13), let minute = digits(14..<16), let second = digits(17..<19), hour <= 23, minute <= 59, second <= 59 else { return false }",
" let leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)",
" let days: Int",
" switch month { case 1, 3, 5, 7, 8, 10, 12: days = 31; case 4, 6, 9, 11: days = 30; case 2: days = leap ? 29 : 28; default: return false }",
" guard day > 0 && day <= days else { return false }",
" if bytes.count == 20 { return true }",
" let fraction = bytes[20..<(bytes.count - 1)]",
" return bytes[19] == 46 && !fraction.isEmpty && fraction.count <= 9 && fraction.allSatisfy { $0 >= 48 && $0 <= 57 } && fraction.last != 48",
"}",
"private func validCanonicalUUID(_ value: String) -> Bool {",
" let bytes = Array(value.utf8)",
" guard bytes.count == 36, bytes[8] == 45, bytes[13] == 45, bytes[18] == 45, bytes[23] == 45, value != \"00000000-0000-0000-0000-000000000000\" else { return false }",
" return bytes.enumerated().allSatisfy { index, byte in [8, 13, 18, 23].contains(index) || (byte >= 48 && byte <= 57) || (byte >= 97 && byte <= 102) }",
"}",
"",
]
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 rejectDuplicateJSONKeys(data); return try JSONDecoder().decode(Self.self, from: data) }", " public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }", "}", ""])
out.extend([
"public func deviceRegistrationProofTranscript(serverID: Data, principalID: Data, deviceID: Data, challenge: Data, expiryUnixMilliseconds: Int64) throws -> Data {",
" for (field, value, length) in [(\"server_id\", serverID, 16), (\"principal_id\", principalID, 16), (\"device_id\", deviceID, 16), (\"challenge\", challenge, 32)] {",
" if value.count != length { throw ContractValidationError(field: field, code: \"invalid_length\") }",
" }",
" if expiryUnixMilliseconds < 0 { throw ContractValidationError(field: \"expiry_unix_milliseconds\", code: \"minimum\") }",
" var transcript = Data(\"versevdi-device-proof-v1\".utf8)",
" transcript.append(serverID)",
" transcript.append(principalID)",
" transcript.append(deviceID)",
" transcript.append(challenge)",
" var expiry = UInt64(expiryUnixMilliseconds).bigEndian",
" Swift.withUnsafeBytes(of: &expiry) { transcript.append(contentsOf: $0) }",
" return transcript",
"}",
"",
"public extension TunnelAdmissionRequest {",
" func deviceAdmissionTranscript() -> Data {",
" var fields = [sessionId, gatewayId, audience, grant, String(reconnectSequence), clientNonce, capabilities.transport, capabilities.framing, capabilities.media, capabilities.sourceRateControl, String(capabilities.videoProfiles.count), String(capabilities.audioProfiles.count)]",
" for profile in capabilities.videoProfiles { fields.append(contentsOf: [profile.codec, String(profile.bitDepth), profile.chromaSubsampling, profile.colorSpace, profile.transferFunction]) }",
" for profile in capabilities.audioProfiles { fields.append(contentsOf: [profile.codec, String(profile.sampleRateHz), String(profile.channels), profile.channelLayout, String(profile.packetDurationMs)]) }",
" 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 commonVideo = selected.videoProfiles",
" var commonAudio = selected.audioProfiles",
" for profile in profiles.dropFirst() {",
" try profile.validate()",
" if profile.transport != selected.transport || profile.framing != selected.framing || profile.media != selected.media || profile.sourceRateControl != selected.sourceRateControl { throw ContractValidationError(field: \"capabilities\", code: \"no_overlap\") }",
" commonVideo = commonVideo.filter { profile.videoProfiles.contains($0) }",
" commonAudio = commonAudio.filter { profile.audioProfiles.contains($0) }",
" if commonVideo.isEmpty || commonAudio.isEmpty { throw ContractValidationError(field: \"capabilities\", code: \"no_overlap\") }",
" }",
" return try CapabilityProfile(transport: selected.transport, framing: selected.framing, media: selected.media, sourceRateControl: selected.sourceRateControl, videoProfiles: commonVideo, audioProfiles: commonAudio)",
" }",
"}",
"",
])
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)