fix(protocol): fence gateway work recovery

This commit is contained in:
sechmachine
2026-08-13 07:43:42 +07:00
parent 8eacc4fda9
commit 6e18bc9ee6
21 changed files with 572 additions and 146 deletions
+99 -16
View File
@@ -69,6 +69,21 @@ let audio = try AudioProfile(codec: "opus", sampleRateHz: 48000, channels: 2, ch
let display = try DisplayMode(resolutionWidth: 2560, resolutionHeight: 1440, fps: 120)
let adjustment = try SessionAdjustment(displayReason: "none", bitrateReason: "none")
let descriptor = try SelectedSessionDescriptor(videoProfile: video, audioProfile: audio, displayMode: display, bitrateTargetKbps: 40000, bitrateMaximumKbps: 50000, adjustment: adjustment, mediaTimestampBasis: "gateway-send-wall-clock-ms")
let operationId = "12345678-1234-1234-1234-123456789abc"
_ = try GatewayQualityWorkRequest(version: "1", sessionId: "session", gatewayId: "gateway", reconnectSequence: 2, acquisition: "poll", operationId: nil, revision: nil, leaseGeneration: nil, currentAppliedRevision: nil)
_ = try GatewayQualityWorkRequest(version: "1", sessionId: "session", gatewayId: "gateway", reconnectSequence: 2, acquisition: "prompt", operationId: operationId, revision: 7, leaseGeneration: nil, currentAppliedRevision: nil)
_ = try GatewayQualityWorkRequest(version: "1", sessionId: "session", gatewayId: "gateway", reconnectSequence: 2, acquisition: "observation", operationId: operationId, revision: 7, leaseGeneration: 3, currentAppliedRevision: 6)
do {
_ = try GatewayQualityWorkRequest(version: "1", sessionId: "session", gatewayId: "gateway", reconnectSequence: 2, acquisition: "poll", operationId: operationId, revision: 7, leaseGeneration: nil, currentAppliedRevision: nil)
fatalError("poll accepted unknown operation coordinates")
} catch { }
_ = try GatewayQualityAck(version: "1", sessionId: "session", gatewayId: "gateway", reconnectSequence: 2, operationId: operationId, revision: 7, leaseGeneration: 3, outcome: "applied", currentAppliedRevision: 7, failureCode: nil)
_ = try GatewayQualityAck(version: "1", sessionId: "session", gatewayId: "gateway", reconnectSequence: 2, operationId: operationId, revision: 7, leaseGeneration: 3, outcome: "proven_prior", currentAppliedRevision: 6, failureCode: nil)
_ = try GatewayQualityAck(version: "1", sessionId: "session", gatewayId: "gateway", reconnectSequence: 2, operationId: operationId, revision: 7, leaseGeneration: 3, outcome: "unknown", currentAppliedRevision: nil, failureCode: nil)
do {
_ = try GatewayQualityAck(version: "1", sessionId: "session", gatewayId: "gateway", reconnectSequence: 2, operationId: operationId, revision: 7, leaseGeneration: 3, outcome: "applied", currentAppliedRevision: 6, failureCode: nil)
fatalError("applied ack accepted a contradictory revision")
} catch { }
let capability = try CapabilityProfile(
transport: "quic-tls13", framing: "datagram-v1", media: "encoded",
sourceRateControl: "server", videoProfiles: [video], audioProfiles: [audio]
@@ -191,6 +206,19 @@ do {
_ = try CapabilityProfile.decodeJSON(duplicateCapability)
fatalError("capability accepted duplicate JSON keys")
} catch { }
do {
_ = try CapabilityProfile.decodeJSON(Data(repeating: 32, count: 1_048_577))
fatalError("capability accepted oversized JSON")
} catch let error as ContractValidationError {
guard error.code == "payload_too_large" else { fatalError("oversized JSON was not rejected before parsing") }
}
let deeplyNested = Data((String(repeating: "[", count: 65) + "null" + String(repeating: "]", count: 65)).utf8)
do {
_ = try CapabilityProfile.decodeJSON(deeplyNested)
fatalError("capability accepted over-deep JSON")
} catch let error as ContractValidationError {
guard error.code == "nesting_too_deep" else { fatalError("over-deep JSON was not rejected before decoding") }
}
for field in ["version", "session_id", "gateway_id", "audience", "reconnect_sequence", "expires_at", "capabilities", "selected_descriptor"] {
var missing = clientAuthorityObject
missing.removeValue(forKey: field)
@@ -385,6 +413,15 @@ fn main() {
let display = DisplayMode::new(2560, 1440, 120).unwrap();
let adjustment = SessionAdjustment::new("none".into(), "none".into()).unwrap();
let descriptor = SelectedSessionDescriptor::new(video.clone(), audio.clone(), display.clone(), 40000, 50000, adjustment, "gateway-send-wall-clock-ms".into()).unwrap();
let operation_id = "12345678-1234-1234-1234-123456789abc".to_string();
assert!(GatewayQualityWorkRequest::new("1".into(), "session".into(), "gateway".into(), 2, "poll".into(), None, None, None, None).is_ok());
assert!(GatewayQualityWorkRequest::new("1".into(), "session".into(), "gateway".into(), 2, "prompt".into(), Some(operation_id.clone()), Some(7), None, None).is_ok());
assert!(GatewayQualityWorkRequest::new("1".into(), "session".into(), "gateway".into(), 2, "observation".into(), Some(operation_id.clone()), Some(7), Some(3), Some(6)).is_ok());
assert!(GatewayQualityWorkRequest::new("1".into(), "session".into(), "gateway".into(), 2, "poll".into(), Some(operation_id.clone()), Some(7), None, None).is_err());
assert!(GatewayQualityAck::new("1".into(), "session".into(), "gateway".into(), 2, operation_id.clone(), 7, 3, "applied".into(), Some(7), None).is_ok());
assert!(GatewayQualityAck::new("1".into(), "session".into(), "gateway".into(), 2, operation_id.clone(), 7, 3, "proven_prior".into(), Some(6), None).is_ok());
assert!(GatewayQualityAck::new("1".into(), "session".into(), "gateway".into(), 2, operation_id.clone(), 7, 3, "unknown".into(), None, None).is_ok());
assert!(GatewayQualityAck::new("1".into(), "session".into(), "gateway".into(), 2, operation_id, 7, 3, "applied".into(), Some(6), None).is_err());
let capabilities = CapabilityProfile::new(
"quic-tls13".into(), "datagram-v1".into(), "encoded".into(),
"server".into(), vec![video.clone()], vec![audio.clone()],
@@ -544,22 +581,68 @@ fn main() {
)
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),
("selected_descriptor", 8),
]
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}"
)
expected_protobuf_messages = {
"ClientSessionAuthority": [
("version", 1),
("session_id", 2),
("gateway_id", 3),
("audience", 4),
("reconnect_sequence", 5),
("expires_at", 6),
("capabilities", 7),
("selected_descriptor", 8),
],
"GatewayQualityWorkRequest": [
("version", 1),
("session_id", 2),
("gateway_id", 3),
("reconnect_sequence", 4),
("operation_id", 5),
("revision", 6),
("current_applied_revision", 7),
("acquisition", 8),
("lease_generation", 9),
],
"GatewayQualityWork": [
("version", 1),
("session_id", 2),
("gateway_id", 3),
("reconnect_sequence", 4),
("operation_id", 5),
("revision", 6),
("lease_expires_at", 7),
("selected_descriptor", 8),
("current_applied_revision", 9),
("lease_generation", 10),
],
"GatewayQualityAck": [
("version", 1),
("session_id", 2),
("gateway_id", 3),
("reconnect_sequence", 4),
("operation_id", 5),
("revision", 6),
("outcome", 7),
("current_applied_revision", 8),
("failure_code", 9),
("lease_generation", 10),
],
"GatewayStopWorkRequest": [
("version", 1),
("session_id", 2),
("gateway_id", 3),
("reconnect_sequence", 4),
("operation_id", 5),
("acquisition", 6),
],
}
for message, expected_protobuf_fields in expected_protobuf_messages.items():
actual_protobuf_fields = protobuf_message_fields(message)
if actual_protobuf_fields != expected_protobuf_fields:
raise RuntimeError(
f"{message} 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: