feat(protocol): define native session credentials
Verify Protocol / module (push) Successful in 1m13s
Verify Protocol / verify (push) Successful in 35s

This commit is contained in:
sechmachine
2026-08-11 18:38:19 +07:00
parent 79d9e49497
commit afcd5d99db
32 changed files with 1565 additions and 149 deletions
+51 -1
View File
@@ -150,6 +150,12 @@ def go_validation(definition: dict[str, Any]) -> list[str]:
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\"}}) }} }}")
@@ -359,6 +365,8 @@ def rust_validation(definition: dict[str, Any]) -> list[str]:
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("type") == "integer":
@@ -372,6 +380,12 @@ def rust_validation(definition: dict[str, Any]) -> list[str]:
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\")); }} }}")
@@ -429,6 +443,19 @@ def generate_rust(defs: dict[str, dict[str, Any]], schema_hash: str, compatibili
" _ => 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'",
"}",
"",
]
for name in sorted(defs):
@@ -524,7 +551,7 @@ def swift_validation(definition: dict[str, Any]) -> list[str]:
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\") }}")
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("type") == "integer":
@@ -538,6 +565,12 @@ def swift_validation(definition: dict[str, Any]) -> list[str]:
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\") }}")
@@ -581,6 +614,23 @@ def generate_swift(defs: dict[str, dict[str, Any]], schema_hash: str, compatibil
" 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",
"}",
"",
]
for name in sorted(defs):
+60 -6
View File
@@ -38,7 +38,7 @@ func main() {
if len(fields) != 5 {
panic("invalid fixture row")
}
actual := evaluate(fields[2], fields[3])
actual := evaluate(fields[1], fields[2], fields[3])
if actual != fields[4] {
panic(fmt.Sprintf("%s: got %s want %s", fields[0], actual, fields[4]))
}
@@ -49,7 +49,7 @@ func main() {
fmt.Printf("Go conformance passed normalized=%s fixtures=%s\n", normalizedDigest(results), fixtureHash)
}
func evaluate(kind, input string) string {
func evaluate(version, kind, input string) string {
parts := map[string]string{}
for _, item := range strings.Split(input, ";") {
pair := strings.SplitN(item, "=", 2)
@@ -59,7 +59,7 @@ func evaluate(kind, input string) string {
}
switch kind {
case "version":
if input == "1" || input == "0" || input == "-1" {
if input == "2" || input == "1" || input == "0" {
return "valid"
}
return "invalid:unsupported_version"
@@ -81,7 +81,7 @@ func evaluate(kind, input string) string {
Version: parts["version"], Purpose: parts["purpose"], SessionID: "session-1",
ReconnectSequence: 0,
Gateway: protocol.ManifestGateway{
ID: parts["gateway_id"], Addresses: []string{"gateway.control.test:443"}, PublicIdentity: parts["gateway_id"],
ID: parts["gateway_id"], Addresses: []string{"gateway.control.test:443"}, PublicIdentity: parts["public_identity"],
},
Tunnel: protocol.ManifestTunnel{Versions: []string{parts["protocol"] + "/1"}, Features: []string{"control.v1"}},
Profile: protocol.ManifestProfile{ID: "standard", Bounds: protocol.ManifestBounds{MinimumKbps: 1, TargetKbps: 2, MaximumKbps: 3}},
@@ -99,6 +99,60 @@ func evaluate(kind, input string) string {
return "valid"
}
return "invalid:unsupported_clipboard"
case "session_request":
if version != "2" {
return "invalid:unsupported_version"
}
if _, supplied := parts["policy_snapshot"]; supplied {
return "invalid:forbidden_field"
}
value := protocol.SessionRequest{
ClientDeviceID: parts["client_device_id"], DeviceKeyID: parts["device_key_id"],
PoolID: parts["pool_id"], IdempotencyKey: parts["idempotency_key"],
}
if value.Validate() == nil {
return "valid"
}
return "invalid:required"
case "browser_authenticated_session":
if _, hasDevice := parts["client_device_id"]; hasDevice {
return "invalid:forbidden_field"
}
if _, hasKey := parts["device_key_id"]; hasKey {
return "invalid:forbidden_field"
}
value := protocol.BrowserAuthenticatedSession{
Username: parts["username"], Provider: parts["provider"], Roles: []string{parts["roles"]},
Role: parts["role"],
}
if value.Validate() == nil {
return "valid"
}
return "invalid:invalid_session"
case "native_authenticated_session":
clientDeviceID, hasDevice := parts["client_device_id"]
deviceKeyID, hasKey := parts["device_key_id"]
if !hasDevice || !hasKey {
return "invalid:required"
}
value := protocol.NativeAuthenticatedSession{
Username: parts["username"], Provider: parts["provider"], Roles: []string{parts["roles"]}, Role: parts["role"],
NativeIdentity: protocol.NativeSessionIdentity{ClientDeviceID: clientDeviceID, DeviceKeyID: deviceKeyID},
}
if value.Validate() == nil {
return "valid"
}
return "invalid:invalid_session"
case "native_tunnel_credential":
value := protocol.NativeTunnelCredential{
ClientDeviceID: parts["client_device_id"], DeviceKeyID: parts["device_key_id"],
CertificateChainPem: parts["certificate_chain_pem"], TrustBundlePem: parts["trust_bundle_pem"],
ExpiresAt: parts["expires_at"],
}
if value.Validate() == nil {
return "valid"
}
return "invalid:invalid_credential"
case "event":
sequence, sequenceErr := strconv.ParseInt(parts["sequence"], 10, 64)
payloadBytes, payloadErr := strconv.Atoi(parts["payload_bytes"])
@@ -126,8 +180,8 @@ func evaluate(kind, input string) string {
return "valid"
case "tunnel":
feature := parts["feature"]
registered := feature == "control.v1" || feature == "display.request.v1" || feature == "input.absolute.v1" || feature == "input.scroll.v1"
if (parts["offered"] == "1" || parts["offered"] == "0" || parts["offered"] == "-1") && registered {
registered := feature == "control.v1" || feature == "control.v2" || feature == "display.request.v1" || feature == "input.absolute.v1" || feature == "input.scroll.v1"
if (parts["offered"] == "2" || parts["offered"] == "1" || parts["offered"] == "0") && registered {
return "valid"
}
if !registered {
+56 -6
View File
@@ -9,10 +9,10 @@ fn values(input: &str) -> std::collections::BTreeMap<String, String> {
.collect()
}
fn evaluate(kind: &str, input: &str) -> &'static str {
fn evaluate(version: &str, kind: &str, input: &str) -> &'static str {
let values = values(input);
match kind {
"version" if matches!(input, "1" | "0" | "-1") => "valid",
"version" if matches!(input, "2" | "1" | "0") => "valid",
"version" => "invalid:unsupported_version",
"page" => match values.get("limit").and_then(|value| value.parse::<i64>().ok()) {
Some(limit) if (1..=100).contains(&limit) => "valid",
@@ -24,12 +24,62 @@ fn evaluate(kind: &str, input: &str) -> &'static str {
"manifest"
if values.get("version").map(String::as_str) == Some("1")
&& values.contains_key("gateway_id")
&& values.contains_key("public_identity")
&& values.get("grant").map_or(false, |value| value.len() >= 43)
&& values.get("purpose").map(String::as_str) == Some("launch") => "valid",
"manifest" => "invalid:invalid_manifest",
"clipboard" if values.get("encoding").map(String::as_str) == Some("utf-8")
&& !values.contains_key("file") => "valid",
"clipboard" => "invalid:unsupported_clipboard",
"session_request" if version != "2" => "invalid:unsupported_version",
"session_request" if values.contains_key("policy_snapshot") => "invalid:forbidden_field",
"session_request" => match SessionRequest::new(
values.get("client_device_id").cloned().unwrap_or_default(),
values.get("device_key_id").cloned().unwrap_or_default(),
values.get("pool_id").cloned().unwrap_or_default(),
values.get("idempotency_key").cloned().unwrap_or_default(),
None,
) {
Ok(_) => "valid",
Err(_) => "invalid:required",
},
"browser_authenticated_session" if values.contains_key("client_device_id") || values.contains_key("device_key_id") => "invalid:forbidden_field",
"browser_authenticated_session" => match BrowserAuthenticatedSession::new(
values.get("username").cloned().unwrap_or_default(),
values.get("provider").cloned().unwrap_or_default(),
vec![values.get("roles").cloned().unwrap_or_default()],
values.get("role").cloned().unwrap_or_default(),
) {
Ok(_) => "valid",
Err(_) => "invalid:invalid_session",
},
"native_authenticated_session" if !values.contains_key("client_device_id") || !values.contains_key("device_key_id") => "invalid:required",
"native_authenticated_session" => {
let identity = match NativeSessionIdentity::new(values["client_device_id"].clone(), values["device_key_id"].clone()) {
Ok(identity) => identity,
Err(_) => return "invalid:required",
};
match NativeAuthenticatedSession::new(
values.get("username").cloned().unwrap_or_default(),
values.get("provider").cloned().unwrap_or_default(),
vec![values.get("roles").cloned().unwrap_or_default()],
values.get("role").cloned().unwrap_or_default(),
identity,
) {
Ok(_) => "valid",
Err(_) => "invalid:invalid_session",
}
}
"native_tunnel_credential" => match NativeTunnelCredential::new(
values.get("client_device_id").cloned().unwrap_or_default(),
values.get("device_key_id").cloned().unwrap_or_default(),
values.get("certificate_chain_pem").cloned().unwrap_or_default(),
values.get("trust_bundle_pem").cloned().unwrap_or_default(),
values.get("expires_at").cloned().unwrap_or_default(),
) {
Ok(_) => "valid",
Err(_) => "invalid:invalid_credential",
},
"event" if values.get("version").map(String::as_str) != Some("1") => {
"invalid:unsupported_version"
}
@@ -46,9 +96,9 @@ fn evaluate(kind: &str, input: &str) -> &'static str {
"event" if values.get("sequence").and_then(|value| value.parse::<i64>().ok()).map_or(true, |sequence| sequence < 1)
|| !values.contains_key("correlation_id") => "invalid:required",
"event" => "valid",
"tunnel" if matches!(values.get("offered").map(String::as_str), Some("1") | Some("0") | Some("-1"))
&& matches!(values.get("feature").map(String::as_str), Some("control.v1") | Some("display.request.v1") | Some("input.absolute.v1") | Some("input.scroll.v1")) => "valid",
"tunnel" if !matches!(values.get("feature").map(String::as_str), Some("control.v1") | Some("display.request.v1") | Some("input.absolute.v1") | Some("input.scroll.v1")) => {
"tunnel" if matches!(values.get("offered").map(String::as_str), Some("2") | Some("1") | Some("0"))
&& matches!(values.get("feature").map(String::as_str), Some("control.v1") | Some("control.v2") | Some("display.request.v1") | Some("input.absolute.v1") | Some("input.scroll.v1")) => "valid",
"tunnel" if !matches!(values.get("feature").map(String::as_str), Some("control.v1") | Some("control.v2") | Some("display.request.v1") | Some("input.absolute.v1") | Some("input.scroll.v1")) => {
"invalid:unsupported_feature"
}
"tunnel" => "invalid:unsupported_version",
@@ -283,7 +333,7 @@ fn main() {
for line in lines {
let fields: Vec<&str> = line.split('\t').collect();
assert_eq!(fields.len(), 5);
let actual = evaluate(fields[2], fields[3]);
let actual = evaluate(fields[1], fields[2], fields[3]);
assert_eq!(actual, fields[4], "{}", fields[0]);
results.push(format!("{}\t{}", fields[0], actual));
}
+38 -6
View File
@@ -9,17 +9,49 @@ func values(_ input: String) -> [String: String] {
return result
}
func evaluate(_ kind: String, _ input: String) -> String {
func evaluate(_ version: String, _ kind: String, _ input: String) -> String {
let values = values(input)
switch kind {
case "version": return ["1", "0", "-1"].contains(input) ? "valid" : "invalid:unsupported_version"
case "version": return ["2", "1", "0"].contains(input) ? "valid" : "invalid:unsupported_version"
case "page":
guard let raw = values["limit"], let limit = Int(raw), (1...100).contains(limit) else { return "invalid:invalid_limit" }
return "valid"
case "manifest":
for key in ["provider_url", "vm_address", "password", "private_key"] where values[key] != nil { return "invalid:forbidden_field" }
return values["version"] == "1" && values["gateway_id"] != nil && (values["grant"]?.utf8.count ?? 0) >= 43 && values["purpose"] == "launch" ? "valid" : "invalid:invalid_manifest"
return values["version"] == "1" && values["gateway_id"] != nil && values["public_identity"] != nil && (values["grant"]?.utf8.count ?? 0) >= 43 && values["purpose"] == "launch" ? "valid" : "invalid:invalid_manifest"
case "clipboard": return values["encoding"] == "utf-8" && values["file"] == nil ? "valid" : "invalid:unsupported_clipboard"
case "session_request":
guard version == "2" else { return "invalid:unsupported_version" }
if values["policy_snapshot"] != nil { return "invalid:forbidden_field" }
guard (try? SessionRequest(
clientDeviceId: values["client_device_id"] ?? "", deviceKeyId: values["device_key_id"] ?? "",
poolId: values["pool_id"] ?? "", idempotencyKey: values["idempotency_key"] ?? "",
requestedDisplayMode: nil
)) != nil else { return "invalid:required" }
return "valid"
case "browser_authenticated_session":
guard values["client_device_id"] == nil, values["device_key_id"] == nil else { return "invalid:forbidden_field" }
guard (try? BrowserAuthenticatedSession(
username: values["username"] ?? "", provider: values["provider"] ?? "",
roles: [values["roles"] ?? ""], role: values["role"] ?? ""
)) != nil else { return "invalid:invalid_session" }
return "valid"
case "native_authenticated_session":
guard let identity = try? NativeSessionIdentity(
clientDeviceId: values["client_device_id"] ?? "", deviceKeyId: values["device_key_id"] ?? ""
), values["client_device_id"] != nil, values["device_key_id"] != nil else { return "invalid:required" }
guard (try? NativeAuthenticatedSession(
username: values["username"] ?? "", provider: values["provider"] ?? "",
roles: [values["roles"] ?? ""], role: values["role"] ?? "", nativeIdentity: identity
)) != nil else { return "invalid:invalid_session" }
return "valid"
case "native_tunnel_credential":
guard (try? NativeTunnelCredential(
clientDeviceId: values["client_device_id"] ?? "", deviceKeyId: values["device_key_id"] ?? "",
certificateChainPem: values["certificate_chain_pem"] ?? "", trustBundlePem: values["trust_bundle_pem"] ?? "",
expiresAt: values["expires_at"] ?? ""
)) != nil else { return "invalid:invalid_credential" }
return "valid"
case "event":
guard values["version"] == "1" else { return "invalid:unsupported_version" }
if let after = Int(values["after"] ?? ""), let earliest = Int(values["earliest"] ?? ""), after > 0, earliest > 0, after < earliest - 1 { return "invalid:gap" }
@@ -27,8 +59,8 @@ func evaluate(_ kind: String, _ input: String) -> String {
guard let sequence = Int(values["sequence"] ?? ""), sequence > 0, values["correlation_id"] != nil else { return "invalid:required" }
return "valid"
case "tunnel":
let registered = ["control.v1", "display.request.v1", "input.absolute.v1", "input.scroll.v1"].contains(values["feature"] ?? "")
if ["1", "0", "-1"].contains(values["offered"] ?? "") && registered { return "valid" }
let registered = ["control.v1", "control.v2", "display.request.v1", "input.absolute.v1", "input.scroll.v1"].contains(values["feature"] ?? "")
if ["2", "1", "0"].contains(values["offered"] ?? "") && registered { return "valid" }
return registered ? "invalid:unsupported_version" : "invalid:unsupported_feature"
case "datagram": return classifyDatagram(values["hex"] ?? "")
case "gateway_input": return classifyGatewayInput(values["hex"] ?? "")
@@ -197,7 +229,7 @@ struct ConformanceMain {
for line in lines {
let fields = line.split(separator: "\t", omittingEmptySubsequences: false).map(String.init)
precondition(fields.count == 5)
let actual = evaluate(fields[2], fields[3])
let actual = evaluate(fields[1], fields[2], fields[3])
precondition(actual == fields[4], fields[0])
results.append("\(fields[0])\t\(actual)")
}
+84 -20
View File
@@ -35,6 +35,9 @@ 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"]
@@ -114,23 +117,16 @@ for invalid in [
fatalError("invalid display mode was accepted")
} catch { }
}
let allocationPolicy = try AllocationPolicy(
minimumKbps: 1000, targetKbps: 2000, maximumKbps: 3000, tier: "standard",
audience: "versevdi-gateway", protocolValue: "verse", protocolVersion: 1,
grantTtlSeconds: 60, reservationLeaseSeconds: 300
)
let legacyDisplayRequest = try SessionRequest(
let policyFreeV2Request = try SessionRequest(
clientDeviceId: "device-1", deviceKeyId: "key-1", poolId: "pool-1",
idempotencyKey: "request-1", policySnapshot: allocationPolicy,
requestedDisplayMode: nil
idempotencyKey: "request-1", requestedDisplayMode: nil
).encodeJSON()
guard !String(data: legacyDisplayRequest, encoding: .utf8)!.contains("requested_display_mode") else {
fatalError("legacy request encoded an absent display mode")
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", policySnapshot: allocationPolicy,
requestedDisplayMode: displayMode
idempotencyKey: "request-1", requestedDisplayMode: displayMode
)
guard try SessionRequest.decodeJSON(displayRequest.encodeJSON()).requestedDisplayMode == displayMode else {
fatalError("display mode did not round-trip")
@@ -141,6 +137,52 @@ 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
@@ -207,6 +249,9 @@ do {
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()],
@@ -260,20 +305,39 @@ fn main() {
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 allocation_policy = AllocationPolicy::new(
1000, 2000, 3000, "standard".into(), "versevdi-gateway".into(),
"verse".into(), 1, 60, 300,
).unwrap();
let legacy_display_request = SessionRequest::new(
let policy_free_v2_request = SessionRequest::new(
"device-1".into(), "key-1".into(), "pool-1".into(), "request-1".into(),
allocation_policy.clone(), None,
None,
).unwrap();
assert!(legacy_display_request.requestedDisplayMode().is_none());
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(),
allocation_policy, Some(display_mode.clone()),
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());
+63 -2
View File
@@ -24,7 +24,7 @@ def main() -> int:
assert set(definition["required"]).issubset(definition["properties"]), name
compatibility = json.loads((ROOT / "compatibility.json").read_text(encoding="utf-8"))
assert set([compatibility["current"], compatibility["n_minus_1"], compatibility["n_minus_2"]]) == {"1", "0", "-1"}
assert [compatibility["current"], compatibility["n_minus_1"], compatibility["n_minus_2"]] == ["2", "1", "0"]
assert len(set(compatibility["unsupported"])) == len(compatibility["unsupported"])
for registry in ("registries/features.json", "registries/datagrams.json"):
@@ -38,7 +38,7 @@ def main() -> int:
feature_registry = json.loads((ROOT / "registries/features.json").read_text(encoding="utf-8"))
registered_features = {entry["id"] for entry in feature_registry["features"]}
assert {"display.request.v1", "input.absolute.v1", "input.scroll.v1"}.issubset(registered_features)
assert {"control.v1", "control.v2", "display.request.v1", "input.absolute.v1", "input.scroll.v1"}.issubset(registered_features)
display_mode = defs["DisplayMode"]
assert display_mode["required"] == ["resolution_width", "resolution_height", "fps"]
@@ -54,10 +54,57 @@ def main() -> int:
assert field not in defs[owner]["required"]
assert defs[owner]["properties"][field] == {"$ref": "#/$defs/DisplayMode"}
session_request = defs["SessionRequest"]
assert "policy_snapshot" not in session_request["required"]
assert "policy_snapshot" not in session_request["properties"]
assert "policy_snapshot" in defs["BrokerSession"]["required"]
assert defs["BrokerSession"]["properties"]["policy_snapshot"] == {"$ref": "#/$defs/AllocationPolicy"}
native_identity = defs["NativeSessionIdentity"]
assert native_identity["required"] == ["client_device_id", "device_key_id"]
browser_session = defs["BrowserAuthenticatedSession"]
assert browser_session["required"] == ["username", "provider", "roles", "role"]
assert "native_identity" not in browser_session["properties"]
native_session = defs["NativeAuthenticatedSession"]
assert native_session["required"] == ["username", "provider", "roles", "role", "native_identity"]
assert native_session["properties"]["native_identity"] == {"$ref": "#/$defs/NativeSessionIdentity"}
for session_definition in (browser_session, native_session):
assert session_definition["properties"]["roles"]["items"] == {
"type": "string", "minLength": 1, "maxLength": 64, "x-max-bytes": 64
}
tunnel_credential = defs["NativeTunnelCredential"]
assert tunnel_credential["required"] == [
"client_device_id", "device_key_id", "certificate_chain_pem", "trust_bundle_pem", "expires_at"
]
manifest = json.loads((ROOT / "fixtures/valid/manifest.json").read_text(encoding="utf-8"))
assert set(manifest).issubset(set(defs["ConnectionManifest"]["properties"]))
public_identity = manifest["gateway"]["public_identity"]
assert public_identity == "gateway.control.test"
assert public_identity not in {
manifest["gateway"]["id"],
*manifest["gateway"]["addresses"],
"sha256:" + "00" * 32,
"apollo-provider-1",
}
forbidden = json.loads((ROOT / "fixtures/invalid/manifest-provider-field.json").read_text(encoding="utf-8"))
assert "provider_url" not in defs["ConnectionManifest"]["properties"] and "provider_url" in forbidden
session_request_fixture = json.loads((ROOT / "fixtures/valid/session-request.json").read_text(encoding="utf-8"))
assert "policy_snapshot" not in session_request_fixture
rejected_policy_fixture = json.loads((ROOT / "fixtures/invalid/session-request-policy-snapshot.json").read_text(encoding="utf-8"))
assert "policy_snapshot" in rejected_policy_fixture
browser_session_fixture = json.loads((ROOT / "fixtures/valid/authenticated-browser-session.json").read_text(encoding="utf-8"))
assert "native_identity" not in browser_session_fixture
native_session_fixture = json.loads((ROOT / "fixtures/valid/authenticated-native-session.json").read_text(encoding="utf-8"))
assert set(native_session_fixture["native_identity"]) == {"client_device_id", "device_key_id"}
partial_identity_fixture = json.loads((ROOT / "fixtures/invalid/authenticated-session-partial-native-identity.json").read_text(encoding="utf-8"))
assert set(partial_identity_fixture["native_identity"]) != {"client_device_id", "device_key_id"}
browser_native_fixture = json.loads((ROOT / "fixtures/invalid/browser-session-native-identity.json").read_text(encoding="utf-8"))
assert "native_identity" in browser_native_fixture
native_missing_fixture = json.loads((ROOT / "fixtures/invalid/native-session-missing-identity.json").read_text(encoding="utf-8"))
assert "native_identity" not in native_missing_fixture
tunnel_credential_fixture = json.loads((ROOT / "fixtures/valid/native-tunnel-credential.json").read_text(encoding="utf-8"))
assert set(tunnel_credential_fixture) == set(tunnel_credential["required"])
expected_header = "id\tversion\tkind\tinput\texpected"
ids = set()
@@ -87,6 +134,20 @@ def main() -> int:
assert "openapi: 3.1.0" in openapi
assert "/api/v1/auth/refresh:" in openapi and "/api/v1/resources:" in openapi and "/api/v1/events:" in openapi
assert "provider_url" not in openapi and "vm_address" not in openapi
session_endpoint = openapi.split(" /api/v1/auth/session:", 1)[1].split("\n /api/", 1)[0]
assert "$defs/BrowserAuthenticatedSession" in session_endpoint
assert "$defs/NativeAuthenticatedSession" in session_endpoint
login_endpoint = openapi.split(" /api/v1/auth/login:", 1)[1].split("\n /api/", 1)[0]
assert "$defs/BrowserAuthenticatedSession" in login_endpoint
assert "$defs/NativeAuthenticatedSession" not in login_endpoint
tunnel_endpoint = openapi.split(" /api/v1/auth/tunnel-credentials:", 1)[1].split("\n /api/", 1)[0]
assert "- nativeBearer: []" in tunnel_endpoint
assert "browserSession" not in tunnel_endpoint and "requestBody:" not in tunnel_endpoint
assert "$defs/NativeTunnelCredential" in tunnel_endpoint
assert "Cache-Control:" in tunnel_endpoint and "const: no-store" in tunnel_endpoint
assert defs["ManifestGateway"]["properties"]["public_identity"]["description"] == (
"Exact TLS server name; distinct from dial addresses, gateway UUIDs, certificate fingerprints, and provider identities."
)
print("Protocol source validation passed")
return 0