553 lines
26 KiB
Python
553 lines
26 KiB
Python
#!/usr/bin/env python3
|
|
"""Compile and exercise strict generated Swift and Rust gateway contracts."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pathlib
|
|
import re
|
|
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 protobuf_message_fields(name: str) -> list[tuple[str, int]]:
|
|
result = subprocess.run(
|
|
["protoc", "--decode=google.protobuf.FileDescriptorSet", "google/protobuf/descriptor.proto"],
|
|
input=(ROOT / "gen/protobuf/tunnel-v1.pb").read_bytes(),
|
|
capture_output=True,
|
|
check=False,
|
|
)
|
|
if result.returncode != 0:
|
|
raise RuntimeError(result.stderr.decode())
|
|
lines = result.stdout.decode().splitlines()
|
|
marker = f' name: "{name}"'
|
|
try:
|
|
name_index = lines.index(marker)
|
|
start = max(index for index in range(name_index) if lines[index] == " message_type {")
|
|
except (ValueError, StopIteration) as exc:
|
|
raise RuntimeError(f"protobuf descriptor missing message {name}") from exc
|
|
depth = 0
|
|
block: list[str] = []
|
|
for line in lines[start:]:
|
|
depth += line.count("{") - line.count("}")
|
|
block.append(line)
|
|
if depth == 0:
|
|
break
|
|
return [(field, int(number)) for field, number in re.findall(r' field \{\n name: "([^"]+)"\n number: (\d+)', "\n".join(block))]
|
|
|
|
|
|
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 proofServerID = Data(repeating: 1, count: 16)
|
|
let proofPrincipalID = Data(repeating: 2, count: 16)
|
|
let proofDeviceID = Data(repeating: 3, count: 16)
|
|
let proofChallenge = Data(repeating: 4, count: 32)
|
|
let proofTranscript = try deviceRegistrationProofTranscript(
|
|
serverID: proofServerID, principalID: proofPrincipalID, deviceID: proofDeviceID,
|
|
challenge: proofChallenge, expiryUnixMilliseconds: 1
|
|
)
|
|
guard proofTranscript.count == 112,
|
|
String(data: proofTranscript.prefix(24), encoding: .utf8) == "versevdi-device-proof-v1",
|
|
Array(proofTranscript.suffix(8)) == [0, 0, 0, 0, 0, 0, 0, 1] else {
|
|
fatalError("unexpected device registration proof transcript")
|
|
}
|
|
let invalidProofInputs: [(String, String, Data, Data, Data, Data, Int64)] = [
|
|
("server-short", "server_id", Data(repeating: 0, count: 15), proofPrincipalID, proofDeviceID, proofChallenge, 0),
|
|
("server-long", "server_id", Data(repeating: 0, count: 17), proofPrincipalID, proofDeviceID, proofChallenge, 0),
|
|
("principal-short", "principal_id", proofServerID, Data(repeating: 0, count: 15), proofDeviceID, proofChallenge, 0),
|
|
("principal-long", "principal_id", proofServerID, Data(repeating: 0, count: 17), proofDeviceID, proofChallenge, 0),
|
|
("device-short", "device_id", proofServerID, proofPrincipalID, Data(repeating: 0, count: 15), proofChallenge, 0),
|
|
("device-long", "device_id", proofServerID, proofPrincipalID, Data(repeating: 0, count: 17), proofChallenge, 0),
|
|
("challenge-short", "challenge", proofServerID, proofPrincipalID, proofDeviceID, Data(repeating: 0, count: 31), 0),
|
|
("challenge-long", "challenge", proofServerID, proofPrincipalID, proofDeviceID, Data(repeating: 0, count: 33), 0),
|
|
("negative-expiry", "expiry_unix_milliseconds", proofServerID, proofPrincipalID, proofDeviceID, proofChallenge, -1),
|
|
]
|
|
for (name, field, serverID, principalID, deviceID, challenge, expiry) in invalidProofInputs {
|
|
do {
|
|
_ = try deviceRegistrationProofTranscript(
|
|
serverID: serverID, principalID: principalID, deviceID: deviceID,
|
|
challenge: challenge, expiryUnixMilliseconds: expiry
|
|
)
|
|
fatalError("\(name) was accepted")
|
|
} catch let error as ContractValidationError {
|
|
guard error.field == field,
|
|
error.code == (field == "expiry_unix_milliseconds" ? "minimum" : "invalid_length") else {
|
|
fatalError("\(name) returned the wrong validation error")
|
|
}
|
|
}
|
|
}
|
|
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 { }
|
|
}
|
|
let clientAuthority = try ClientSessionAuthority(
|
|
version: "1", sessionId: "session", gatewayId: "gateway", audience: "audience",
|
|
reconnectSequence: 2, expiresAt: "2099-01-01T00:00:00Z", capabilities: capability
|
|
)
|
|
let clientAuthorityJSON = try clientAuthority.encodeJSON()
|
|
let clientAuthorityObject = try JSONSerialization.jsonObject(with: clientAuthorityJSON) as! [String: Any]
|
|
guard Set(clientAuthorityObject.keys) == Set([
|
|
"version", "session_id", "gateway_id", "audience", "reconnect_sequence", "expires_at", "capabilities"
|
|
]), !String(data: clientAuthorityJSON, encoding: .utf8)!.contains("provider_") else {
|
|
fatalError("client authority was not exactly provider-free")
|
|
}
|
|
_ = try ClientSessionAuthority.decodeJSON(clientAuthorityJSON)
|
|
for field in ["version", "session_id", "gateway_id", "audience", "reconnect_sequence", "expires_at", "capabilities"] {
|
|
var missing = clientAuthorityObject
|
|
missing.removeValue(forKey: field)
|
|
do {
|
|
_ = try ClientSessionAuthority.decodeJSON(try JSONSerialization.data(withJSONObject: missing))
|
|
fatalError("client authority accepted missing \(field)")
|
|
} catch { }
|
|
}
|
|
for (field, value) in [
|
|
("provider_profile", "apollo"),
|
|
("provider_identity", "provider-1"),
|
|
("provider_url", "https://provider.invalid"),
|
|
("management_host", "provider.invalid"),
|
|
("unknown", "true"),
|
|
] {
|
|
var injected = clientAuthorityObject
|
|
injected[field] = value
|
|
do {
|
|
_ = try ClientSessionAuthority.decodeJSON(try JSONSerialization.data(withJSONObject: injected))
|
|
fatalError("client authority accepted injected \(field)")
|
|
} catch { }
|
|
}
|
|
for expiresAt in ["not-a-time", "2099-01-01T00:00:00+00:00", "2099-01-01T00:00:00.100Z"] {
|
|
var invalidExpiry = clientAuthorityObject
|
|
invalidExpiry["expires_at"] = expiresAt
|
|
do {
|
|
_ = try ClientSessionAuthority.decodeJSON(try JSONSerialization.data(withJSONObject: invalidExpiry))
|
|
fatalError("client authority accepted invalid expiry")
|
|
} catch { }
|
|
}
|
|
do {
|
|
_ = try ClientSessionAuthority.decodeJSON(clientAuthorityJSON + Data(" {}".utf8))
|
|
fatalError("client authority accepted trailing JSON")
|
|
} 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());
|
|
let proof_server_id = vec![1u8; 16];
|
|
let proof_principal_id = vec![2u8; 16];
|
|
let proof_device_id = vec![3u8; 16];
|
|
let proof_challenge = vec![4u8; 32];
|
|
let proof = device_registration_proof_transcript(
|
|
&proof_server_id, &proof_principal_id, &proof_device_id, &proof_challenge, 1,
|
|
).unwrap();
|
|
assert_eq!(proof.len(), 112);
|
|
assert_eq!(&proof[..24], b"versevdi-device-proof-v1");
|
|
assert_eq!(&proof[104..], &[0, 0, 0, 0, 0, 0, 0, 1]);
|
|
for (server_id, principal_id, device_id, challenge, expiry, field, code) in [
|
|
(vec![0; 15], proof_principal_id.clone(), proof_device_id.clone(), proof_challenge.clone(), 0, "server_id", "invalid_length"),
|
|
(vec![0; 17], proof_principal_id.clone(), proof_device_id.clone(), proof_challenge.clone(), 0, "server_id", "invalid_length"),
|
|
(proof_server_id.clone(), vec![0; 15], proof_device_id.clone(), proof_challenge.clone(), 0, "principal_id", "invalid_length"),
|
|
(proof_server_id.clone(), vec![0; 17], proof_device_id.clone(), proof_challenge.clone(), 0, "principal_id", "invalid_length"),
|
|
(proof_server_id.clone(), proof_principal_id.clone(), vec![0; 15], proof_challenge.clone(), 0, "device_id", "invalid_length"),
|
|
(proof_server_id.clone(), proof_principal_id.clone(), vec![0; 17], proof_challenge.clone(), 0, "device_id", "invalid_length"),
|
|
(proof_server_id.clone(), proof_principal_id.clone(), proof_device_id.clone(), vec![0; 31], 0, "challenge", "invalid_length"),
|
|
(proof_server_id.clone(), proof_principal_id.clone(), proof_device_id.clone(), vec![0; 33], 0, "challenge", "invalid_length"),
|
|
(proof_server_id.clone(), proof_principal_id.clone(), proof_device_id.clone(), proof_challenge.clone(), -1, "expiry_unix_milliseconds", "minimum"),
|
|
] {
|
|
assert_eq!(
|
|
device_registration_proof_transcript(&server_id, &principal_id, &device_id, &challenge, expiry),
|
|
Err(ValidationError::new(field, code)),
|
|
);
|
|
}
|
|
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());
|
|
let client_authority = ClientSessionAuthority::new(
|
|
"1".into(), "session".into(), "gateway".into(), "audience".into(), 2,
|
|
"2099-01-01T00:00:00Z".into(), capabilities.clone(),
|
|
).unwrap();
|
|
assert_eq!(client_authority.sessionId(), "session");
|
|
assert_eq!(client_authority.capabilities(), &capabilities);
|
|
assert!(ClientSessionAuthority::new(
|
|
"1".into(), "session".into(), "gateway".into(), "audience".into(), 2,
|
|
"not-a-time".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(), 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)
|
|
expected_protobuf_fields = [
|
|
("version", 1),
|
|
("session_id", 2),
|
|
("gateway_id", 3),
|
|
("audience", 4),
|
|
("reconnect_sequence", 5),
|
|
("expires_at", 6),
|
|
("capabilities", 7),
|
|
]
|
|
actual_protobuf_fields = protobuf_message_fields("ClientSessionAuthority")
|
|
if actual_protobuf_fields != expected_protobuf_fields:
|
|
raise RuntimeError(
|
|
f"ClientSessionAuthority protobuf fields = {actual_protobuf_fields}; "
|
|
f"want {expected_protobuf_fields}"
|
|
)
|
|
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())
|