391 lines
18 KiB
Python
391 lines
18 KiB
Python
#!/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"]
|
|
)
|
|
guard currentWireVersion == "2", nMinus1WireVersion == "1", nMinus2WireVersion == "0" else {
|
|
fatalError("unexpected control wire compatibility declaration")
|
|
}
|
|
_ = try CapabilityProfile(
|
|
transport: "quic-tls13", framing: "datagram-v2", media: "encoded",
|
|
audio: "encoded", sourceRateControl: "server", clientDecode: ["h264-opus"]
|
|
)
|
|
do {
|
|
_ = try CapabilityProfile(
|
|
transport: "quic-tls13", framing: "datagram-v3", media: "encoded",
|
|
audio: "encoded", sourceRateControl: "server", clientDecode: ["h264-opus"]
|
|
)
|
|
fatalError("unregistered framing was accepted")
|
|
} catch { }
|
|
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), deviceSignature: String(repeating: "s", count: 86), capabilities: capability
|
|
)
|
|
_ = request
|
|
let transcript = "versevdi/tunnel-admission/v17:session7:gateway8:audience43:" + String(repeating: "g", count: 43) + "1:016:" + String(repeating: "n", count: 16) + "10:quic-tls1311:datagram-v17:encoded7:encoded6:server1:19:h264-opus"
|
|
guard String(data: request.deviceAdmissionTranscript(), encoding: .utf8) == transcript else {
|
|
fatalError("unexpected device admission transcript")
|
|
}
|
|
let incompatible = try CapabilityProfile(
|
|
transport: "quic-tls13", framing: "datagram-v1", media: "encoded",
|
|
audio: "encoded", sourceRateControl: "server", clientDecode: ["hevc-opus"]
|
|
)
|
|
let gatewayCapability = try CapabilityProfile(
|
|
transport: "quic-tls13", framing: "datagram-v1", media: "encoded",
|
|
audio: "encoded", sourceRateControl: "server", clientDecode: ["hevc-opus", "h264-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") }
|
|
guard try CapabilityProfile.intersection([gatewayCapability, capability]).clientDecode == ["h264-opus"] else {
|
|
fatalError("ordered registered profile intersection changed")
|
|
}
|
|
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 { }
|
|
let displayMode = try DisplayMode(resolutionWidth: 2560, resolutionHeight: 1440, fps: 120)
|
|
for invalid in [
|
|
{ try DisplayMode(resolutionWidth: 319, resolutionHeight: 1440, fps: 120) },
|
|
{ try DisplayMode(resolutionWidth: 2560, resolutionHeight: 199, fps: 120) },
|
|
{ try DisplayMode(resolutionWidth: 2560, resolutionHeight: 1440, fps: 241) },
|
|
] {
|
|
do {
|
|
_ = try invalid()
|
|
fatalError("invalid display mode was accepted")
|
|
} catch { }
|
|
}
|
|
let policyFreeV2Request = try SessionRequest(
|
|
clientDeviceId: "device-1", deviceKeyId: "key-1", poolId: "pool-1",
|
|
idempotencyKey: "request-1", requestedDisplayMode: nil
|
|
).encodeJSON()
|
|
guard !String(data: policyFreeV2Request, encoding: .utf8)!.contains("requested_display_mode") else {
|
|
fatalError("wire-v2 request encoded an absent display mode")
|
|
}
|
|
let displayRequest = try SessionRequest(
|
|
clientDeviceId: "device-1", deviceKeyId: "key-1", poolId: "pool-1",
|
|
idempotencyKey: "request-1", requestedDisplayMode: displayMode
|
|
)
|
|
guard try SessionRequest.decodeJSON(displayRequest.encodeJSON()).requestedDisplayMode == displayMode else {
|
|
fatalError("display mode did not round-trip")
|
|
}
|
|
var nullDisplayRequest = try JSONSerialization.jsonObject(with: displayRequest.encodeJSON()) as! [String: Any]
|
|
nullDisplayRequest["requested_display_mode"] = NSNull()
|
|
do {
|
|
_ = try SessionRequest.decodeJSON(try JSONSerialization.data(withJSONObject: nullDisplayRequest))
|
|
fatalError("explicit null display mode was accepted")
|
|
} catch { }
|
|
let nativeIdentity = try NativeSessionIdentity(clientDeviceId: "device-1", deviceKeyId: "key-1")
|
|
let browserSession = try BrowserAuthenticatedSession(
|
|
username: "alice", provider: "local", roles: ["user"], role: "user"
|
|
)
|
|
guard !String(data: try browserSession.encodeJSON(), encoding: .utf8)!.contains("native_identity") else {
|
|
fatalError("browser session encoded native identity")
|
|
}
|
|
let nativeSession = try NativeAuthenticatedSession(
|
|
username: "alice", provider: "local", roles: ["user"], role: "user", nativeIdentity: nativeIdentity
|
|
)
|
|
guard try NativeAuthenticatedSession.decodeJSON(nativeSession.encodeJSON()).nativeIdentity == nativeIdentity else {
|
|
fatalError("native session identity did not round-trip")
|
|
}
|
|
do {
|
|
_ = try BrowserAuthenticatedSession.decodeJSON(nativeSession.encodeJSON())
|
|
fatalError("browser session accepted native identity")
|
|
} catch { }
|
|
do {
|
|
_ = try NativeAuthenticatedSession.decodeJSON(browserSession.encodeJSON())
|
|
fatalError("native session accepted missing identity")
|
|
} catch { }
|
|
var partialNativeSession = try JSONSerialization.jsonObject(with: nativeSession.encodeJSON()) as! [String: Any]
|
|
partialNativeSession["native_identity"] = ["client_device_id": "device-1"]
|
|
do {
|
|
_ = try NativeAuthenticatedSession.decodeJSON(try JSONSerialization.data(withJSONObject: partialNativeSession))
|
|
fatalError("partial native identity was accepted")
|
|
} catch { }
|
|
for roles in [[""], [String(repeating: "r", count: 65)]] {
|
|
do {
|
|
_ = try BrowserAuthenticatedSession(username: "alice", provider: "local", roles: roles, role: "user")
|
|
fatalError("invalid role item length was accepted")
|
|
} catch { }
|
|
}
|
|
_ = try NativeTunnelCredential(
|
|
clientDeviceId: "device-1", deviceKeyId: "key-1", certificateChainPem: "certificate",
|
|
trustBundlePem: "trust", expiresAt: "2099-01-01T00:00:00Z"
|
|
)
|
|
for expiresAt in ["2099-01-01T00:00:00+00:00", "2099-01-01T00:00:00.100Z"] {
|
|
do {
|
|
_ = try NativeTunnelCredential(
|
|
clientDeviceId: "device-1", deviceKeyId: "key-1", certificateChainPem: "certificate",
|
|
trustBundlePem: "trust", expiresAt: expiresAt
|
|
)
|
|
fatalError("noncanonical RFC3339 UTC timestamp was accepted")
|
|
} catch { }
|
|
}
|
|
let streamPolicy = try ProviderStreamPolicy(
|
|
resolutionWidth: 2560, resolutionHeight: 1440, fps: 120,
|
|
codec: "HEVC", bitrateKbps: 40000, audioEnabled: true
|
|
)
|
|
guard streamPolicy.codec == "HEVC" else { fatalError("stream policy changed") }
|
|
for invalid in [
|
|
{ try ProviderStreamPolicy(resolutionWidth: 319, resolutionHeight: 1440, fps: 120, codec: "HEVC", bitrateKbps: 40000, audioEnabled: true) },
|
|
{ try ProviderStreamPolicy(resolutionWidth: 2560, resolutionHeight: 1440, fps: 241, codec: "HEVC", bitrateKbps: 40000, audioEnabled: true) },
|
|
{ try ProviderStreamPolicy(resolutionWidth: 2560, resolutionHeight: 1440, fps: 120, codec: "VP9", bitrateKbps: 40000, audioEnabled: true) },
|
|
] {
|
|
do {
|
|
_ = try invalid()
|
|
fatalError("invalid stream policy was accepted")
|
|
} catch { }
|
|
}
|
|
let telemetry = try GatewayTelemetry(
|
|
admittedSessions: 1, admissionRejects: 2, reconnects: 3, drainTransitions: 4,
|
|
mediaDrops: 5, mediaPackets: 6, mediaBytes: 7, queueDelayMicros: 8,
|
|
processingDelayMicros: 9, processingSamples: 10, pacingDelayMicros: 11,
|
|
providerErrors: 12, inputRejected: 13, controlRttMicros: 14,
|
|
controlJitterMicros: 15, controlLossPpm: 16, pendingReliable: 17,
|
|
providerState: "ready"
|
|
)
|
|
guard telemetry.mediaBytes == 7 else { fatalError("gateway telemetry changed") }
|
|
do {
|
|
_ = try GatewayTelemetry(
|
|
admittedSessions: 1, admissionRejects: 2, reconnects: 3, drainTransitions: 4,
|
|
mediaDrops: 5, mediaPackets: 6, mediaBytes: 7, queueDelayMicros: 8,
|
|
processingDelayMicros: 9, processingSamples: 10, pacingDelayMicros: 11,
|
|
providerErrors: 12, inputRejected: 13, controlRttMicros: 14,
|
|
controlJitterMicros: 15, controlLossPpm: 1000001, pendingReliable: 17,
|
|
providerState: "ready"
|
|
)
|
|
fatalError("invalid gateway telemetry was accepted")
|
|
} catch { }
|
|
for text in [
|
|
String(repeating: "a", count: 65536),
|
|
String(repeating: "é", count: 32768),
|
|
String(repeating: "\\\"", count: 32768),
|
|
] {
|
|
let clipboard = try GatewayClipboardText(
|
|
direction: "client_to_provider", text: text, encoding: "utf-8",
|
|
loopToken: "abcdefghijklmnop"
|
|
)
|
|
let decoded = try GatewayClipboardText.decodeJSON(clipboard.encodeJSON())
|
|
guard decoded.text == text else { fatalError("clipboard text changed during round-trip") }
|
|
}
|
|
do {
|
|
_ = try GatewayClipboardText(
|
|
direction: "client_to_provider", text: String(repeating: "a", count: 65537),
|
|
encoding: "utf-8", loopToken: "abcdefghijklmnop"
|
|
)
|
|
fatalError("oversized clipboard text was 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() {
|
|
assert_eq!(CURRENT_WIRE_VERSION, "2");
|
|
assert_eq!(N_MINUS_1_WIRE_VERSION, "1");
|
|
assert_eq!(N_MINUS_2_WIRE_VERSION, "0");
|
|
let capabilities = CapabilityProfile::new(
|
|
"quic-tls13".into(), "datagram-v1".into(), "encoded".into(),
|
|
"encoded".into(), "server".into(), vec!["h264-opus".into()],
|
|
).unwrap();
|
|
assert!(CapabilityProfile::new(
|
|
"quic-tls13".into(), "datagram-v2".into(), "encoded".into(),
|
|
"encoded".into(), "server".into(), vec!["h264-opus".into()],
|
|
).is_ok());
|
|
assert!(CapabilityProfile::new(
|
|
"quic-tls13".into(), "datagram-v3".into(), "encoded".into(),
|
|
"encoded".into(), "server".into(), vec!["h264-opus".into()],
|
|
).is_err());
|
|
let request = TunnelAdmissionRequest::new(
|
|
"1".into(), "session".into(), "gateway".into(), "audience".into(),
|
|
"g".repeat(43), 0, "n".repeat(16), "s".repeat(86), capabilities.clone(),
|
|
).unwrap();
|
|
let transcript = "versevdi/tunnel-admission/v17:session7:gateway8:audience43:".to_string()
|
|
+ &"g".repeat(43) + "1:016:" + &"n".repeat(16)
|
|
+ "10:quic-tls1311:datagram-v17:encoded7:encoded6:server1:19:h264-opus";
|
|
assert_eq!(request.device_admission_transcript(), transcript.into_bytes());
|
|
assert!(TunnelAdmissionRequest::new(
|
|
"2".into(), "session".into(), "gateway".into(), "audience".into(),
|
|
"g".repeat(43), 0, "n".repeat(16), "s".repeat(86), capabilities.clone(),
|
|
).is_err());
|
|
assert!(TunnelAdmissionRequest::new(
|
|
"0".into(), "session".into(), "gateway".into(), "audience".into(),
|
|
"g".repeat(43), 0, "n".repeat(16), "s".repeat(86), capabilities.clone(),
|
|
).is_err());
|
|
assert!(TunnelAdmissionRequest::new(
|
|
"1".into(), "session".into(), "gateway".into(), "audience".into(),
|
|
"g".repeat(43), 0, "short".into(), "s".repeat(86), 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(), vec!["hevc-opus".into()],
|
|
).unwrap();
|
|
let gateway_capability = CapabilityProfile::new(
|
|
"quic-tls13".into(), "datagram-v1".into(), "encoded".into(),
|
|
"encoded".into(), "server".into(), vec!["hevc-opus".into(), "h264-opus".into()],
|
|
).unwrap();
|
|
assert_eq!(
|
|
intersect_capability_profiles(&[gateway_capability, capabilities.clone()]).unwrap().clientDecode(),
|
|
&vec!["h264-opus".to_string()],
|
|
);
|
|
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());
|
|
let display_mode = DisplayMode::new(2560, 1440, 120).unwrap();
|
|
assert!(DisplayMode::new(319, 1440, 120).is_err());
|
|
assert!(DisplayMode::new(2560, 199, 120).is_err());
|
|
assert!(DisplayMode::new(2560, 1440, 241).is_err());
|
|
let policy_free_v2_request = SessionRequest::new(
|
|
"device-1".into(), "key-1".into(), "pool-1".into(), "request-1".into(),
|
|
None,
|
|
).unwrap();
|
|
assert!(policy_free_v2_request.requestedDisplayMode().is_none());
|
|
let display_request = SessionRequest::new(
|
|
"device-1".into(), "key-1".into(), "pool-1".into(), "request-1".into(),
|
|
Some(display_mode.clone()),
|
|
).unwrap();
|
|
assert_eq!(display_request.requestedDisplayMode(), &Some(display_mode));
|
|
let native_identity = NativeSessionIdentity::new("device-1".into(), "key-1".into()).unwrap();
|
|
assert!(BrowserAuthenticatedSession::new(
|
|
"alice".into(), "local".into(), vec!["user".into()], "user".into(),
|
|
).is_ok());
|
|
assert!(NativeAuthenticatedSession::new(
|
|
"alice".into(), "local".into(), vec!["user".into()], "user".into(), native_identity,
|
|
).is_ok());
|
|
assert!(BrowserAuthenticatedSession::new(
|
|
"alice".into(), "local".into(), vec![String::new()], "user".into(),
|
|
).is_err());
|
|
assert!(BrowserAuthenticatedSession::new(
|
|
"alice".into(), "local".into(), vec!["r".repeat(65)], "user".into(),
|
|
).is_err());
|
|
assert!(NativeTunnelCredential::new(
|
|
"device-1".into(), "key-1".into(), "certificate".into(), "trust".into(),
|
|
"2099-01-01T00:00:00Z".into(),
|
|
).is_ok());
|
|
for expires_at in ["2099-01-01T00:00:00+00:00", "2099-01-01T00:00:00.100Z"] {
|
|
assert!(NativeTunnelCredential::new(
|
|
"device-1".into(), "key-1".into(), "certificate".into(), "trust".into(),
|
|
expires_at.into(),
|
|
).is_err());
|
|
}
|
|
assert!(ProviderStreamPolicy::new(
|
|
2560, 1440, 120, "HEVC".into(), 40000, true,
|
|
).is_ok());
|
|
assert!(ProviderStreamPolicy::new(
|
|
319, 1440, 120, "HEVC".into(), 40000, true,
|
|
).is_err());
|
|
assert!(ProviderStreamPolicy::new(
|
|
2560, 1440, 241, "HEVC".into(), 40000, true,
|
|
).is_err());
|
|
assert!(ProviderStreamPolicy::new(
|
|
2560, 1440, 120, "VP9".into(), 40000, true,
|
|
).is_err());
|
|
assert!(GatewayTelemetry::new(
|
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, "ready".into(),
|
|
).is_ok());
|
|
assert!(GatewayTelemetry::new(
|
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1000001, 17, "ready".into(),
|
|
).is_err());
|
|
assert!(GatewayTelemetry::new(
|
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, "provider.example:47984".into(),
|
|
).is_err());
|
|
for text in [
|
|
"a".repeat(65536),
|
|
"é".repeat(32768),
|
|
"\\\"".repeat(32768),
|
|
] {
|
|
assert!(GatewayClipboardText::new(
|
|
"client_to_provider".into(), text, "utf-8".into(), "abcdefghijklmnop".into(),
|
|
).is_ok());
|
|
}
|
|
assert!(GatewayClipboardText::new(
|
|
"client_to_provider".into(), "a".repeat(65537), "utf-8".into(),
|
|
"abcdefghijklmnop".into(),
|
|
).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())
|