feat(protocol): validate generated gateway contracts

This commit is contained in:
sechmachine
2026-07-29 10:03:37 +07:00
parent 7c145001c0
commit 0bec3ec131
8 changed files with 2882 additions and 472 deletions
+5 -2
View File
@@ -1,4 +1,4 @@
.PHONY: verify generate proto-lint proto-breaking source-verify scope-verify conformance frame-verify go-test binding-compile clean-generated
.PHONY: verify generate proto-lint proto-breaking source-verify scope-verify conformance frame-verify go-test binding-compile strict-contracts clean-generated
PYTHON ?= python3
PROTOC ?= protoc
@@ -29,6 +29,9 @@ binding-compile:
rustc --crate-type lib gen/rust/protocol.rs -o /tmp/versevdi-protocol-generated.rlib
swiftc -typecheck gen/swift/Protocol.swift
strict-contracts:
$(PYTHON) -B tools/test_generated_contracts.py
conformance:
$(PYTHON) -B tools/fixture_digest.py
go run ./tools/go-conformance
@@ -40,4 +43,4 @@ frame-verify:
clean-generated:
$(PYTHON) tools/generate.py --check
verify: generate proto-lint proto-breaking source-verify scope-verify go-test binding-compile conformance frame-verify clean-generated
verify: generate proto-lint proto-breaking source-verify scope-verify go-test binding-compile strict-contracts conformance frame-verify clean-generated
+30
View File
@@ -414,6 +414,9 @@ func (v AllocationPolicy) Validate() error {
if v.ReservationLeaseSeconds > 3600 {
violations = append(violations, FieldViolation{Field: "reservation_lease_seconds", Code: "maximum"})
}
if v.MinimumKbps > v.TargetKbps || v.TargetKbps > v.MaximumKbps {
violations = append(violations, FieldViolation{Field: "bounds", Code: "invalid_order"})
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
@@ -918,6 +921,9 @@ func (v ChannelFrame) Validate() error {
if len(v.Payload) > 87384 {
violations = append(violations, FieldViolation{Field: "payload", Code: "max_length"})
}
if v.FragmentIndex >= v.FragmentCount {
violations = append(violations, FieldViolation{Field: "fragment_index", Code: "invalid_order"})
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
@@ -2227,6 +2233,9 @@ func (v GatewayRegistration) Validate() error {
if err := v.Capabilities.Validate(); err != nil {
violations = append(violations, FieldViolation{Field: "capabilities", Code: "invalid_object"})
}
if v.ProtocolMinVersion > v.ProtocolMaxVersion {
violations = append(violations, FieldViolation{Field: "protocol_version", Code: "invalid_order"})
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
@@ -2484,6 +2493,9 @@ func (v ManifestBounds) Validate() error {
if v.MaximumKbps > 100000000 {
violations = append(violations, FieldViolation{Field: "maximum_kbps", Code: "maximum"})
}
if v.MinimumKbps > v.TargetKbps || v.TargetKbps > v.MaximumKbps {
violations = append(violations, FieldViolation{Field: "bounds", Code: "invalid_order"})
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
@@ -4043,3 +4055,21 @@ func EncodeVersionNegotiation(value VersionNegotiation) ([]byte, error) {
}
return json.Marshal(value)
}
var ErrNoCapabilityOverlap = errors.New("no capability overlap")
func IntersectCapabilityProfiles(profiles ...CapabilityProfile) (CapabilityProfile, error) {
if len(profiles) == 0 {
return CapabilityProfile{}, ErrNoCapabilityOverlap
}
selected := profiles[0]
if err := selected.Validate(); err != nil {
return CapabilityProfile{}, ErrNoCapabilityOverlap
}
for _, profile := range profiles[1:] {
if err := profile.Validate(); err != nil || profile != selected {
return CapabilityProfile{}, ErrNoCapabilityOverlap
}
}
return selected, nil
}
+1 -1
View File
@@ -12,7 +12,7 @@
"2"
]
},
"generator_sha256": "cb975bcd42bf77641b6a0f44d5ec7a6fdba1858d6b8a865e0f04e53bad82648c",
"generator_sha256": "88535ecf2b1c926104b10b4ab56f1bc6298e1c3e75490e36ee8581a135bcded7",
"protocol_version": "1.0.0",
"schema_sha256": "b8a69785112bb94d45f47c2250ca59d0bde47e3667b8ad89c9b0e2c4cfb25aec"
}
+1255 -194
View File
File diff suppressed because it is too large Load Diff
+1227 -270
View File
File diff suppressed because it is too large Load Diff
+23
View File
@@ -43,12 +43,35 @@ func TestGatewayContractsRejectUnknownVersionsAndFields(t *testing.T) {
}
for _, invalid := range []string{
strings.Replace(registration, `"version":"1"`, `"version":"2"`, 1),
strings.Replace(registration, `"version":"1"`, `"version":"0"`, 1),
strings.Replace(registration, `"features":["datagram.media"]`, `"features":["datagram.media"],"provider_url":"https://provider.invalid"`, 1),
} {
if _, err := protocol.DecodeGatewayRegistration([]byte(invalid)); err == nil {
t.Fatalf("invalid gateway registration accepted: %s", invalid)
}
}
if _, err := protocol.DecodeGatewayRegistration([]byte("{")); err == nil {
t.Fatal("DecodeGatewayRegistration accepted malformed JSON")
}
}
func TestGatewayRegistrationRejectsInvertedProtocolBounds(t *testing.T) {
registration := `{"version":"1","gateway_id":"gateway-1","instance_identity":"instance-1","certificate_identity":"cert-1","public_identity":"public-1","address":"gateway.test:443","provider_identity":"apollo-provider-1","protocol_min_version":2,"protocol_max_version":1,"connection_capacity":8,"bandwidth_capacity_kbps":100000,"features":["datagram.media"],"capabilities":{"transport":"quic","framing":"datagram-v1","media":"encoded","audio":"encoded","source_rate_control":"server","client_decode":"h264-opus"}}`
if _, err := protocol.DecodeGatewayRegistration([]byte(registration)); err == nil {
t.Fatal("DecodeGatewayRegistration accepted inverted protocol bounds")
}
}
func TestCapabilityIntersectionRejectsNoOverlap(t *testing.T) {
first := protocol.CapabilityProfile{Transport: "quic-tls13", Framing: "datagram-v1", Media: "encoded", Audio: "encoded", SourceRateControl: "server", ClientDecode: "h264-opus"}
if got, err := protocol.IntersectCapabilityProfiles(first, first); err != nil || got != first {
t.Fatalf("IntersectCapabilityProfiles matching profiles = %+v, %v", got, err)
}
second := first
second.ClientDecode = "hevc-opus"
if _, err := protocol.IntersectCapabilityProfiles(first, second); err == nil {
t.Fatal("IntersectCapabilityProfiles accepted profiles without a common codec profile")
}
}
func TestSessionAuthorityRejectsProviderRoute(t *testing.T) {
+205 -5
View File
@@ -151,6 +151,12 @@ def go_validation(definition: dict[str, Any]) -> list[str]:
reference = ref_name(prop)
if reference:
lines.append(f"\tif err := v.{field}.Validate(); err != nil {{ violations = append(violations, FieldViolation{{Field: \"{prop_name}\", Code: \"invalid_object\"}}) }}")
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
@@ -235,6 +241,20 @@ def generate_go(defs: dict[str, dict[str, Any]], schema_hash: str, version: str,
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 }",
"\tfor _, profile := range profiles[1:] {",
"\t\tif err := profile.Validate(); err != nil || profile != selected { return CapabilityProfile{}, ErrNoCapabilityOverlap }",
"\t}",
"\treturn selected, nil",
"}",
"",
])
# 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"')
@@ -272,6 +292,58 @@ def swift_type(prop: dict[str, Any]) -> str:
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 "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("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\")); }}")
item_ref = ref_name(prop.get("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.",
@@ -282,6 +354,10 @@ def generate_rust(defs: dict[str, dict[str, Any]], schema_hash: str, compatibili
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 } } }",
"",
]
for name in sorted(defs):
definition = defs[name]
@@ -292,11 +368,103 @@ def generate_rust(defs: dict[str, dict[str, Any]], schema_hash: str, compatibili
typ = rust_type(prop)
if prop_name not in required:
typ = f"Option<{typ}>"
out.append(f" pub {field}: {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} }}")
out.extend(["}", ""])
out.extend([
"pub fn intersect_capability_profiles(profiles: &[CapabilityProfile]) -> Result<CapabilityProfile, ValidationError> {",
" let 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 != &selected { 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 "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("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\") }}")
item_ref = ref_name(prop.get("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.",
@@ -306,6 +474,8 @@ def generate_swift(defs: dict[str, dict[str, Any]], schema_hash: str, compatibil
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 } }",
"",
]
for name in sorted(defs):
@@ -320,16 +490,46 @@ def generate_swift(defs: dict[str, dict[str, Any]], schema_hash: str, compatibil
out.append(" enum CodingKeys: String, CodingKey {")
for prop_name in definition.get("properties", {}):
out.append(f" case {swift_field(prop_name)} = \"{prop_name}\"")
out.extend([" }", "", " public init(from decoder: Decoder) throws {",])
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:
out.append(f" {field} = try c.decode({typ}.self, forKey: .{field})")
decoded.append(f"{field}: try c.decode({typ}.self, forKey: .{field})")
else:
out.append(f" {field} = try c.decodeIfPresent({typ}.self, forKey: .{field})")
out.extend([" }", "}", ""])
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 CapabilityProfile {",
" static func intersection(_ profiles: [CapabilityProfile]) throws -> CapabilityProfile {",
" guard let selected = profiles.first else { throw ContractValidationError(field: \"capabilities\", code: \"no_overlap\") }",
" try selected.validate()",
" for profile in profiles.dropFirst() {",
" try profile.validate()",
" if profile != selected { throw ContractValidationError(field: \"capabilities\", code: \"no_overlap\") }",
" }",
" return selected",
" }",
"}",
"",
])
return "\n".join(out)
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env python3
"""Compile and exercise strict generated Swift and Rust gateway contracts."""
from __future__ import annotations
import pathlib
import shutil
import subprocess
import tempfile
ROOT = pathlib.Path(__file__).resolve().parents[1]
def run(command: list[str], directory: pathlib.Path) -> None:
result = subprocess.run(command, cwd=directory, text=True, capture_output=True, check=False)
if result.returncode != 0:
raise RuntimeError("%s\n%s%s" % (" ".join(command), result.stdout, result.stderr))
def run_failure(command: list[str], directory: pathlib.Path, expected: str) -> None:
result = subprocess.run(command, cwd=directory, text=True, capture_output=True, check=False)
if result.returncode == 0 or expected not in result.stdout + result.stderr:
raise RuntimeError("expected failure: %s\n%s%s" % (" ".join(command), result.stdout, result.stderr))
def main() -> int:
with tempfile.TemporaryDirectory(prefix="versevdi-generated-contracts-") as temporary:
workspace = pathlib.Path(temporary)
swift = workspace / "main.swift"
swift.write_text(
"""import Foundation
let capability = try CapabilityProfile(
transport: "quic-tls13", framing: "datagram-v1", media: "encoded",
audio: "encoded", sourceRateControl: "server", clientDecode: "h264-opus"
)
let request = try TunnelAdmissionRequest(
version: "1", sessionId: "session", gatewayId: "gateway", audience: "audience",
grant: String(repeating: "g", count: 43), reconnectSequence: 0,
clientNonce: String(repeating: "n", count: 16), capabilities: capability
)
_ = request
let incompatible = try CapabilityProfile(
transport: "quic-tls13", framing: "datagram-v1", media: "encoded",
audio: "encoded", sourceRateControl: "server", clientDecode: "hevc-opus"
)
do {
guard try CapabilityProfile.intersection([capability, capability]) == capability else {
fatalError("matching capability profiles did not intersect")
}
} catch { fatalError("matching capability profiles did not intersect") }
do {
_ = try CapabilityProfile.intersection([capability, incompatible])
fatalError("profiles without overlap were accepted")
} catch { }
let valid = try request.encodeJSON()
var unsupported = try JSONSerialization.jsonObject(with: valid) as! [String: Any]
unsupported["version"] = "2"
var downgrade = try JSONSerialization.jsonObject(with: valid) as! [String: Any]
downgrade["version"] = "0"
var unknown = try JSONSerialization.jsonObject(with: valid) as! [String: Any]
unknown["unknown"] = true
for invalid in [
try JSONSerialization.data(withJSONObject: unsupported),
try JSONSerialization.data(withJSONObject: downgrade),
try JSONSerialization.data(withJSONObject: unknown),
Data("{".utf8),
valid + Data(" {}".utf8),
] {
do {
_ = try TunnelAdmissionRequest.decodeJSON(invalid)
fatalError("invalid tunnel admission request was accepted")
} catch { }
}
do {
_ = try AllocationPolicy(
minimumKbps: 100, targetKbps: 50, maximumKbps: 25, tier: "standard",
audience: "audience", protocolValue: "verse", protocolVersion: 1,
grantTtlSeconds: 60, reservationLeaseSeconds: 300
)
fatalError("invalid allocation bounds were accepted")
} catch { }
""",
encoding="utf-8",
)
run(["swiftc", str(ROOT / "gen/swift/Protocol.swift"), str(swift), "-o", str(workspace / "swift-contracts")], ROOT)
run([str(workspace / "swift-contracts")], ROOT)
rust = workspace / "protocol.rs"
shutil.copyfile(ROOT / "gen/rust/protocol.rs", rust)
with rust.open("a", encoding="utf-8") as output:
output.write(
"""
fn main() {
let capabilities = CapabilityProfile::new(
"quic-tls13".into(), "datagram-v1".into(), "encoded".into(),
"encoded".into(), "server".into(), "h264-opus".into(),
).unwrap();
assert!(TunnelAdmissionRequest::new(
"2".into(), "session".into(), "gateway".into(), "audience".into(),
"g".repeat(43), 0, "n".repeat(16), capabilities.clone(),
).is_err());
assert!(TunnelAdmissionRequest::new(
"0".into(), "session".into(), "gateway".into(), "audience".into(),
"g".repeat(43), 0, "n".repeat(16), capabilities.clone(),
).is_err());
assert!(TunnelAdmissionRequest::new(
"1".into(), "session".into(), "gateway".into(), "audience".into(),
"g".repeat(43), 0, "short".into(), capabilities.clone(),
).is_err());
assert!(intersect_capability_profiles(&[capabilities.clone(), capabilities.clone()]).is_ok());
let incompatible = CapabilityProfile::new(
"quic-tls13".into(), "datagram-v1".into(), "encoded".into(),
"encoded".into(), "server".into(), "hevc-opus".into(),
).unwrap();
assert!(intersect_capability_profiles(&[capabilities, incompatible]).is_err());
assert!(AllocationPolicy::new(
100, 50, 25, "standard".into(), "audience".into(), "verse".into(), 1, 60, 300,
).is_err());
}
"""
)
run(["rustc", str(rust), "-o", str(workspace / "rust-contracts")], ROOT)
run([str(workspace / "rust-contracts")], ROOT)
rust_unknown = workspace / "unknown.rs"
shutil.copyfile(ROOT / "gen/rust/protocol.rs", rust_unknown)
with rust_unknown.open("a", encoding="utf-8") as output:
output.write("\nfn main() { let _ = CapabilityProfile { unknown: String::new() }; }\n")
run_failure(["rustc", str(rust_unknown), "-o", str(workspace / "rust-unknown")], ROOT, "no field named `unknown`")
print("Generated strict contract checks passed")
return 0
if __name__ == "__main__":
raise SystemExit(main())