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
@@ -0,0 +1 @@
{"version":"1","session_id":"session-1","gateway_id":"gateway-1","reconnect_sequence":2,"operation_id":"12345678-1234-1234-1234-123456789abc","revision":7,"lease_generation":3,"outcome":"applied","current_applied_revision":6}
@@ -0,0 +1 @@
{"version":"1","session_id":"session-1","gateway_id":"gateway-1","reconnect_sequence":2,"operation_id":"12345678-1234-1234-1234-123456789ABC","revision":7,"lease_generation":3,"outcome":"unknown"}
@@ -0,0 +1 @@
{"version":"1","session_id":"session-1","gateway_id":"gateway-1","reconnect_sequence":2,"operation_id":"00000000-0000-0000-0000-000000000000","revision":7,"lease_generation":3,"outcome":"unknown"}
@@ -0,0 +1 @@
{"version":"1","session_id":"session-1","gateway_id":"gateway-1","reconnect_sequence":2,"acquisition":"poll","operation_id":"12345678-1234-1234-1234-123456789abc","revision":7}
@@ -0,0 +1 @@
{"operation_id":"12345678-1234-1234-1234-123456789abc","session_id":"session-1","revision":7,"state":"pending","requested_bitrate_preference":{"mode":"auto"},"effective_bitrate_kbps":20000,"governing_policy_version":"policy-1","session_version":2,"created_at":"2099-01-01T00:00:00.100Z","deadline_at":"2099-01-01T00:00:30Z","updated_at":"2099-01-01T00:00:00Z"}
@@ -0,0 +1 @@
{"operation_id":"12345678-1234-1234-1234-123456789abc","session_id":"session-1","revision":7,"state":"pending","requested_bitrate_preference":{"mode":"auto"},"effective_bitrate_kbps":20000,"governing_policy_version":"policy-1","session_version":2,"created_at":"2099-01-01T00:00:00+00:00","deadline_at":"2099-01-01T00:00:30Z","updated_at":"2099-01-01T00:00:00Z"}
+9 -1
View File
@@ -17,20 +17,28 @@
"fixtures/invalid/bitrate-preference-auto-target.json", "fixtures/invalid/bitrate-preference-auto-target.json",
"fixtures/invalid/browser-session-native-identity.json", "fixtures/invalid/browser-session-native-identity.json",
"fixtures/invalid/capability-rc5-opaque.json", "fixtures/invalid/capability-rc5-opaque.json",
"fixtures/invalid/gateway-quality-ack-contradictory.json",
"fixtures/invalid/gateway-quality-ack-uppercase-uuid.json",
"fixtures/invalid/gateway-quality-ack-zero-uuid.json",
"fixtures/invalid/gateway-quality-poll-coordinates.json",
"fixtures/invalid/manifest-provider-field.json", "fixtures/invalid/manifest-provider-field.json",
"fixtures/invalid/native-session-missing-identity.json", "fixtures/invalid/native-session-missing-identity.json",
"fixtures/invalid/provider-stream-policy-rc5.json", "fixtures/invalid/provider-stream-policy-rc5.json",
"fixtures/invalid/quality-operation-noncanonical-fraction.json",
"fixtures/invalid/quality-operation-offset-time.json",
"fixtures/invalid/selected-session-descriptor-provider-field.json", "fixtures/invalid/selected-session-descriptor-provider-field.json",
"fixtures/invalid/session-request-policy-snapshot.json", "fixtures/invalid/session-request-policy-snapshot.json",
"fixtures/invalid/session-request-rc5.json", "fixtures/invalid/session-request-rc5.json",
"fixtures/invalid/video-profile-unknown.json", "fixtures/invalid/video-profile-unknown.json",
"fixtures/valid/authenticated-browser-session.json", "fixtures/valid/authenticated-browser-session.json",
"fixtures/valid/authenticated-native-session.json", "fixtures/valid/authenticated-native-session.json",
"fixtures/valid/gateway-quality-ack-applied.json",
"fixtures/valid/gateway-quality-poll.json",
"fixtures/valid/manifest.json", "fixtures/valid/manifest.json",
"fixtures/valid/native-tunnel-credential.json", "fixtures/valid/native-tunnel-credential.json",
"fixtures/valid/selected-session-descriptor.json", "fixtures/valid/selected-session-descriptor.json",
"fixtures/valid/session-quality-limits.json", "fixtures/valid/session-quality-limits.json",
"fixtures/valid/session-request.json" "fixtures/valid/session-request.json"
], ],
"json_corpus_sha256": "2b111ca1c1723d6063aeafbf08c4e77972b16422ac3a84a054170039b2eff4d3" "json_corpus_sha256": "08dea278526a523d21ea4ca6270ba1e33d44dc0d701681917d7ffc6fb13053d7"
} }
@@ -0,0 +1 @@
{"version":"1","session_id":"session-1","gateway_id":"gateway-1","reconnect_sequence":2,"operation_id":"12345678-1234-1234-1234-123456789abc","revision":7,"lease_generation":3,"outcome":"applied","current_applied_revision":7}
+1
View File
@@ -0,0 +1 @@
{"version":"1","session_id":"session-1","gateway_id":"gateway-1","reconnect_sequence":2,"acquisition":"poll"}
+71 -27
View File
@@ -14,7 +14,7 @@ import (
"time" "time"
) )
const SchemaSHA256 = "614fa11dd1f49b8e10bf21b8c10eadbc1468d16bfcc6ed0a26d29671a5e61300" const SchemaSHA256 = "8c1ed430127cf5774a919f14ff4770b2509d322c8518bf30c412e1e89eeffced"
const ProtocolVersion = "1.0.0" const ProtocolVersion = "1.0.0"
const CurrentWireVersion = "2" const CurrentWireVersion = "2"
const NMinus1WireVersion = "1" const NMinus1WireVersion = "1"
@@ -330,8 +330,9 @@ type GatewayQualityAck struct {
ReconnectSequence int64 `json:"reconnect_sequence"` ReconnectSequence int64 `json:"reconnect_sequence"`
OperationID string `json:"operation_id"` OperationID string `json:"operation_id"`
Revision int64 `json:"revision"` Revision int64 `json:"revision"`
LeaseGeneration int64 `json:"lease_generation"`
Outcome string `json:"outcome"` Outcome string `json:"outcome"`
CurrentAppliedRevision int64 `json:"current_applied_revision"` CurrentAppliedRevision *int64 `json:"current_applied_revision,omitempty"`
FailureCode string `json:"failure_code,omitempty"` FailureCode string `json:"failure_code,omitempty"`
} }
@@ -342,6 +343,7 @@ type GatewayQualityWork struct {
ReconnectSequence int64 `json:"reconnect_sequence"` ReconnectSequence int64 `json:"reconnect_sequence"`
OperationID string `json:"operation_id"` OperationID string `json:"operation_id"`
Revision int64 `json:"revision"` Revision int64 `json:"revision"`
LeaseGeneration int64 `json:"lease_generation"`
LeaseExpiresAt string `json:"lease_expires_at"` LeaseExpiresAt string `json:"lease_expires_at"`
SelectedDescriptor SelectedSessionDescriptor `json:"selected_descriptor"` SelectedDescriptor SelectedSessionDescriptor `json:"selected_descriptor"`
CurrentAppliedRevision *int64 `json:"current_applied_revision,omitempty"` CurrentAppliedRevision *int64 `json:"current_applied_revision,omitempty"`
@@ -352,8 +354,10 @@ type GatewayQualityWorkRequest struct {
SessionID string `json:"session_id"` SessionID string `json:"session_id"`
GatewayID string `json:"gateway_id"` GatewayID string `json:"gateway_id"`
ReconnectSequence int64 `json:"reconnect_sequence"` ReconnectSequence int64 `json:"reconnect_sequence"`
OperationID string `json:"operation_id"` Acquisition string `json:"acquisition"`
Revision int64 `json:"revision"` OperationID string `json:"operation_id,omitempty"`
Revision *int64 `json:"revision,omitempty"`
LeaseGeneration *int64 `json:"lease_generation,omitempty"`
CurrentAppliedRevision *int64 `json:"current_applied_revision,omitempty"` CurrentAppliedRevision *int64 `json:"current_applied_revision,omitempty"`
} }
@@ -397,7 +401,8 @@ type GatewayStopWorkRequest struct {
SessionID string `json:"session_id"` SessionID string `json:"session_id"`
GatewayID string `json:"gateway_id"` GatewayID string `json:"gateway_id"`
ReconnectSequence int64 `json:"reconnect_sequence"` ReconnectSequence int64 `json:"reconnect_sequence"`
OperationID string `json:"operation_id"` Acquisition string `json:"acquisition"`
OperationID string `json:"operation_id,omitempty"`
} }
type GatewayTelemetry struct { type GatewayTelemetry struct {
@@ -3487,13 +3492,19 @@ func (v GatewayQualityAck) Validate() error {
if v.Revision != 0 && v.Revision < 1 { if v.Revision != 0 && v.Revision < 1 {
violations = append(violations, FieldViolation{Field: "revision", Code: "minimum"}) violations = append(violations, FieldViolation{Field: "revision", Code: "minimum"})
} }
if v.LeaseGeneration == 0 {
violations = append(violations, FieldViolation{Field: "lease_generation", Code: "required"})
}
if v.LeaseGeneration != 0 && v.LeaseGeneration < 1 {
violations = append(violations, FieldViolation{Field: "lease_generation", Code: "minimum"})
}
if v.Outcome == "" { if v.Outcome == "" {
violations = append(violations, FieldViolation{Field: "outcome", Code: "required"}) violations = append(violations, FieldViolation{Field: "outcome", Code: "required"})
} }
if v.Outcome != "" && !(v.Outcome == "applied" || v.Outcome == "not_applied" || v.Outcome == "uncertain") { if v.Outcome != "" && !(v.Outcome == "applied" || v.Outcome == "proven_prior" || v.Outcome == "unknown") {
violations = append(violations, FieldViolation{Field: "outcome", Code: "invalid_value"}) violations = append(violations, FieldViolation{Field: "outcome", Code: "invalid_value"})
} }
if v.CurrentAppliedRevision != 0 && v.CurrentAppliedRevision < 0 { if v.CurrentAppliedRevision != nil && *v.CurrentAppliedRevision != 0 && *v.CurrentAppliedRevision < 0 {
violations = append(violations, FieldViolation{Field: "current_applied_revision", Code: "minimum"}) violations = append(violations, FieldViolation{Field: "current_applied_revision", Code: "minimum"})
} }
if len(v.FailureCode) < 1 && v.FailureCode != "" { if len(v.FailureCode) < 1 && v.FailureCode != "" {
@@ -3502,6 +3513,15 @@ func (v GatewayQualityAck) Validate() error {
if len(v.FailureCode) > 128 { if len(v.FailureCode) > 128 {
violations = append(violations, FieldViolation{Field: "failure_code", Code: "max_length"}) violations = append(violations, FieldViolation{Field: "failure_code", Code: "max_length"})
} }
if v.Outcome == "applied" && (v.CurrentAppliedRevision == nil || *v.CurrentAppliedRevision != v.Revision) {
violations = append(violations, FieldViolation{Field: "current_applied_revision", Code: "invalid_tagged_value"})
}
if v.Outcome == "proven_prior" && (v.CurrentAppliedRevision == nil || *v.CurrentAppliedRevision >= v.Revision) {
violations = append(violations, FieldViolation{Field: "current_applied_revision", Code: "invalid_tagged_value"})
}
if v.Outcome == "unknown" && v.CurrentAppliedRevision != nil {
violations = append(violations, FieldViolation{Field: "current_applied_revision", Code: "invalid_tagged_value"})
}
if len(violations) > 0 { if len(violations) > 0 {
return ValidationError{Violations: violations} return ValidationError{Violations: violations}
} }
@@ -3520,12 +3540,12 @@ func DecodeGatewayQualityAck(data []byte) (GatewayQualityAck, error) {
if err := json.Unmarshal(data, &fields); err != nil { if err := json.Unmarshal(data, &fields); err != nil {
return value, err return value, err
} }
if raw, ok := fields["current_applied_revision"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "current_applied_revision", Code: "required"}}}
}
if raw, ok := fields["gateway_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { if raw, ok := fields["gateway_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "gateway_id", Code: "required"}}} return value, ValidationError{Violations: []FieldViolation{{Field: "gateway_id", Code: "required"}}}
} }
if raw, ok := fields["lease_generation"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "lease_generation", Code: "required"}}}
}
if raw, ok := fields["operation_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { if raw, ok := fields["operation_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "operation_id", Code: "required"}}} return value, ValidationError{Violations: []FieldViolation{{Field: "operation_id", Code: "required"}}}
} }
@@ -3616,6 +3636,12 @@ func (v GatewayQualityWork) Validate() error {
if v.Revision != 0 && v.Revision < 1 { if v.Revision != 0 && v.Revision < 1 {
violations = append(violations, FieldViolation{Field: "revision", Code: "minimum"}) violations = append(violations, FieldViolation{Field: "revision", Code: "minimum"})
} }
if v.LeaseGeneration == 0 {
violations = append(violations, FieldViolation{Field: "lease_generation", Code: "required"})
}
if v.LeaseGeneration != 0 && v.LeaseGeneration < 1 {
violations = append(violations, FieldViolation{Field: "lease_generation", Code: "minimum"})
}
if v.LeaseExpiresAt == "" { if v.LeaseExpiresAt == "" {
violations = append(violations, FieldViolation{Field: "lease_expires_at", Code: "required"}) violations = append(violations, FieldViolation{Field: "lease_expires_at", Code: "required"})
} }
@@ -3660,6 +3686,9 @@ func DecodeGatewayQualityWork(data []byte) (GatewayQualityWork, error) {
if raw, ok := fields["lease_expires_at"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { if raw, ok := fields["lease_expires_at"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "lease_expires_at", Code: "required"}}} return value, ValidationError{Violations: []FieldViolation{{Field: "lease_expires_at", Code: "required"}}}
} }
if raw, ok := fields["lease_generation"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "lease_generation", Code: "required"}}}
}
if raw, ok := fields["operation_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { if raw, ok := fields["operation_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "operation_id", Code: "required"}}} return value, ValidationError{Violations: []FieldViolation{{Field: "operation_id", Code: "required"}}}
} }
@@ -3732,8 +3761,11 @@ func (v GatewayQualityWorkRequest) Validate() error {
if v.ReconnectSequence != 0 && v.ReconnectSequence < 0 { if v.ReconnectSequence != 0 && v.ReconnectSequence < 0 {
violations = append(violations, FieldViolation{Field: "reconnect_sequence", Code: "minimum"}) violations = append(violations, FieldViolation{Field: "reconnect_sequence", Code: "minimum"})
} }
if v.OperationID == "" { if v.Acquisition == "" {
violations = append(violations, FieldViolation{Field: "operation_id", Code: "required"}) violations = append(violations, FieldViolation{Field: "acquisition", Code: "required"})
}
if v.Acquisition != "" && !(v.Acquisition == "poll" || v.Acquisition == "prompt" || v.Acquisition == "observation") {
violations = append(violations, FieldViolation{Field: "acquisition", Code: "invalid_value"})
} }
if len(v.OperationID) < 36 && v.OperationID != "" { if len(v.OperationID) < 36 && v.OperationID != "" {
violations = append(violations, FieldViolation{Field: "operation_id", Code: "min_length"}) violations = append(violations, FieldViolation{Field: "operation_id", Code: "min_length"})
@@ -3744,15 +3776,24 @@ func (v GatewayQualityWorkRequest) Validate() error {
if v.OperationID != "" && !validCanonicalUUID(v.OperationID) { if v.OperationID != "" && !validCanonicalUUID(v.OperationID) {
violations = append(violations, FieldViolation{Field: "operation_id", Code: "invalid_uuid"}) violations = append(violations, FieldViolation{Field: "operation_id", Code: "invalid_uuid"})
} }
if v.Revision == 0 { if v.Revision != nil && *v.Revision != 0 && *v.Revision < 1 {
violations = append(violations, FieldViolation{Field: "revision", Code: "required"})
}
if v.Revision != 0 && v.Revision < 1 {
violations = append(violations, FieldViolation{Field: "revision", Code: "minimum"}) violations = append(violations, FieldViolation{Field: "revision", Code: "minimum"})
} }
if v.LeaseGeneration != nil && *v.LeaseGeneration != 0 && *v.LeaseGeneration < 1 {
violations = append(violations, FieldViolation{Field: "lease_generation", Code: "minimum"})
}
if v.CurrentAppliedRevision != nil && *v.CurrentAppliedRevision != 0 && *v.CurrentAppliedRevision < 0 { if v.CurrentAppliedRevision != nil && *v.CurrentAppliedRevision != 0 && *v.CurrentAppliedRevision < 0 {
violations = append(violations, FieldViolation{Field: "current_applied_revision", Code: "minimum"}) violations = append(violations, FieldViolation{Field: "current_applied_revision", Code: "minimum"})
} }
if v.Acquisition == "poll" && (v.OperationID != "" || v.Revision != nil || v.LeaseGeneration != nil || v.CurrentAppliedRevision != nil) {
violations = append(violations, FieldViolation{Field: "acquisition", Code: "invalid_tagged_value"})
}
if v.Acquisition == "prompt" && (v.OperationID == "" || v.Revision == nil || v.LeaseGeneration != nil || v.CurrentAppliedRevision != nil) {
violations = append(violations, FieldViolation{Field: "acquisition", Code: "invalid_tagged_value"})
}
if v.Acquisition == "observation" && (v.OperationID == "" || v.Revision == nil || v.LeaseGeneration == nil || v.CurrentAppliedRevision == nil) {
violations = append(violations, FieldViolation{Field: "acquisition", Code: "invalid_tagged_value"})
}
if len(violations) > 0 { if len(violations) > 0 {
return ValidationError{Violations: violations} return ValidationError{Violations: violations}
} }
@@ -3771,18 +3812,15 @@ func DecodeGatewayQualityWorkRequest(data []byte) (GatewayQualityWorkRequest, er
if err := json.Unmarshal(data, &fields); err != nil { if err := json.Unmarshal(data, &fields); err != nil {
return value, err return value, err
} }
if raw, ok := fields["acquisition"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "acquisition", Code: "required"}}}
}
if raw, ok := fields["gateway_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { if raw, ok := fields["gateway_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "gateway_id", Code: "required"}}} return value, ValidationError{Violations: []FieldViolation{{Field: "gateway_id", Code: "required"}}}
} }
if raw, ok := fields["operation_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "operation_id", Code: "required"}}}
}
if raw, ok := fields["reconnect_sequence"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { if raw, ok := fields["reconnect_sequence"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "reconnect_sequence", Code: "required"}}} return value, ValidationError{Violations: []FieldViolation{{Field: "reconnect_sequence", Code: "required"}}}
} }
if raw, ok := fields["revision"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "revision", Code: "required"}}}
}
if raw, ok := fields["session_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { if raw, ok := fields["session_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "session_id", Code: "required"}}} return value, ValidationError{Violations: []FieldViolation{{Field: "session_id", Code: "required"}}}
} }
@@ -4273,8 +4311,11 @@ func (v GatewayStopWorkRequest) Validate() error {
if v.ReconnectSequence != 0 && v.ReconnectSequence < 0 { if v.ReconnectSequence != 0 && v.ReconnectSequence < 0 {
violations = append(violations, FieldViolation{Field: "reconnect_sequence", Code: "minimum"}) violations = append(violations, FieldViolation{Field: "reconnect_sequence", Code: "minimum"})
} }
if v.OperationID == "" { if v.Acquisition == "" {
violations = append(violations, FieldViolation{Field: "operation_id", Code: "required"}) violations = append(violations, FieldViolation{Field: "acquisition", Code: "required"})
}
if v.Acquisition != "" && !(v.Acquisition == "poll" || v.Acquisition == "prompt") {
violations = append(violations, FieldViolation{Field: "acquisition", Code: "invalid_value"})
} }
if len(v.OperationID) < 36 && v.OperationID != "" { if len(v.OperationID) < 36 && v.OperationID != "" {
violations = append(violations, FieldViolation{Field: "operation_id", Code: "min_length"}) violations = append(violations, FieldViolation{Field: "operation_id", Code: "min_length"})
@@ -4285,6 +4326,9 @@ func (v GatewayStopWorkRequest) Validate() error {
if v.OperationID != "" && !validCanonicalUUID(v.OperationID) { if v.OperationID != "" && !validCanonicalUUID(v.OperationID) {
violations = append(violations, FieldViolation{Field: "operation_id", Code: "invalid_uuid"}) violations = append(violations, FieldViolation{Field: "operation_id", Code: "invalid_uuid"})
} }
if v.Acquisition == "poll" && v.OperationID != "" || v.Acquisition == "prompt" && v.OperationID == "" {
violations = append(violations, FieldViolation{Field: "acquisition", Code: "invalid_tagged_value"})
}
if len(violations) > 0 { if len(violations) > 0 {
return ValidationError{Violations: violations} return ValidationError{Violations: violations}
} }
@@ -4303,12 +4347,12 @@ func DecodeGatewayStopWorkRequest(data []byte) (GatewayStopWorkRequest, error) {
if err := json.Unmarshal(data, &fields); err != nil { if err := json.Unmarshal(data, &fields); err != nil {
return value, err return value, err
} }
if raw, ok := fields["acquisition"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "acquisition", Code: "required"}}}
}
if raw, ok := fields["gateway_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { if raw, ok := fields["gateway_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "gateway_id", Code: "required"}}} return value, ValidationError{Violations: []FieldViolation{{Field: "gateway_id", Code: "required"}}}
} }
if raw, ok := fields["operation_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "operation_id", Code: "required"}}}
}
if raw, ok := fields["reconnect_sequence"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { if raw, ok := fields["reconnect_sequence"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "reconnect_sequence", Code: "required"}}} return value, ValidationError{Violations: []FieldViolation{{Field: "reconnect_sequence", Code: "required"}}}
} }
+2 -2
View File
@@ -12,7 +12,7 @@
"3" "3"
] ]
}, },
"generator_sha256": "9f6e95fac0f3c8389d8111e6ba0a03b48e155b71e345a11640ef824b8b3a4910", "generator_sha256": "c5756780f59a82d8f56762e52f58e22a4d6aca6648157f45e5bad6559b9deeec",
"protocol_version": "1.0.0", "protocol_version": "1.0.0",
"schema_sha256": "614fa11dd1f49b8e10bf21b8c10eadbc1468d16bfcc6ed0a26d29671a5e61300" "schema_sha256": "8c1ed430127cf5774a919f14ff4770b2509d322c8518bf30c412e1e89eeffced"
} }
Binary file not shown.
+58 -28
View File
@@ -1,6 +1,6 @@
// Code generated by tools/generate.py; DO NOT EDIT. // Code generated by tools/generate.py; DO NOT EDIT.
#![allow(non_snake_case)] #![allow(non_snake_case)]
pub const SCHEMA_SHA256: &str = "614fa11dd1f49b8e10bf21b8c10eadbc1468d16bfcc6ed0a26d29671a5e61300"; pub const SCHEMA_SHA256: &str = "8c1ed430127cf5774a919f14ff4770b2509d322c8518bf30c412e1e89eeffced";
pub const CURRENT_WIRE_VERSION: &str = "2"; pub const CURRENT_WIRE_VERSION: &str = "2";
pub const N_MINUS_1_WIRE_VERSION: &str = "1"; pub const N_MINUS_1_WIRE_VERSION: &str = "1";
pub const N_MINUS_2_WIRE_VERSION: &str = "0"; pub const N_MINUS_2_WIRE_VERSION: &str = "0";
@@ -1055,14 +1055,15 @@ pub struct GatewayQualityAck {
reconnectSequence: i64, reconnectSequence: i64,
operationId: String, operationId: String,
revision: i64, revision: i64,
leaseGeneration: i64,
outcome: String, outcome: String,
currentAppliedRevision: i64, currentAppliedRevision: Option<i64>,
failureCode: Option<String>, failureCode: Option<String>,
} }
impl GatewayQualityAck { impl GatewayQualityAck {
pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, operationId: String, revision: i64, outcome: String, currentAppliedRevision: i64, failureCode: Option<String>) -> Result<Self, ValidationError> { pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, operationId: String, revision: i64, leaseGeneration: i64, outcome: String, currentAppliedRevision: Option<i64>, failureCode: Option<String>) -> Result<Self, ValidationError> {
let value = Self { version, sessionId, gatewayId, reconnectSequence, operationId, revision, outcome, currentAppliedRevision, failureCode }; let value = Self { version, sessionId, gatewayId, reconnectSequence, operationId, revision, leaseGeneration, outcome, currentAppliedRevision, failureCode };
value.validate()?; value.validate()?;
Ok(value) Ok(value)
} }
@@ -1080,12 +1081,18 @@ impl GatewayQualityAck {
if self.operationId.len() > 36 { return Err(ValidationError::new("operation_id", "max_length")); } if self.operationId.len() > 36 { return Err(ValidationError::new("operation_id", "max_length")); }
if !valid_canonical_uuid(self.operationId.as_str()) { return Err(ValidationError::new("operation_id", "invalid_uuid")); } if !valid_canonical_uuid(self.operationId.as_str()) { return Err(ValidationError::new("operation_id", "invalid_uuid")); }
if self.revision < 1 { return Err(ValidationError::new("revision", "minimum")); } if self.revision < 1 { return Err(ValidationError::new("revision", "minimum")); }
if self.outcome != "applied" && self.outcome != "not_applied" && self.outcome != "uncertain" { return Err(ValidationError::new("outcome", "invalid_value")); } if self.leaseGeneration < 1 { return Err(ValidationError::new("lease_generation", "minimum")); }
if self.currentAppliedRevision < 0 { return Err(ValidationError::new("current_applied_revision", "minimum")); } if self.outcome != "applied" && self.outcome != "proven_prior" && self.outcome != "unknown" { return Err(ValidationError::new("outcome", "invalid_value")); }
if let Some(value) = &self.currentAppliedRevision {
if *value < 0 { return Err(ValidationError::new("current_applied_revision", "minimum")); }
}
if let Some(value) = &self.failureCode { if let Some(value) = &self.failureCode {
if !value.is_empty() && value.len() < 1 { return Err(ValidationError::new("failure_code", "min_length")); } if !value.is_empty() && value.len() < 1 { return Err(ValidationError::new("failure_code", "min_length")); }
if value.len() > 128 { return Err(ValidationError::new("failure_code", "max_length")); } if value.len() > 128 { return Err(ValidationError::new("failure_code", "max_length")); }
} }
if self.outcome == "applied" && self.currentAppliedRevision != Some(self.revision) { return Err(ValidationError::new("current_applied_revision", "invalid_tagged_value")); }
if self.outcome == "proven_prior" && self.currentAppliedRevision.map_or(true, |current| current >= self.revision) { return Err(ValidationError::new("current_applied_revision", "invalid_tagged_value")); }
if self.outcome == "unknown" && self.currentAppliedRevision.is_some() { return Err(ValidationError::new("current_applied_revision", "invalid_tagged_value")); }
Ok(()) Ok(())
} }
pub fn version(&self) -> &String { &self.version } pub fn version(&self) -> &String { &self.version }
@@ -1094,8 +1101,9 @@ impl GatewayQualityAck {
pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence } pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence }
pub fn operationId(&self) -> &String { &self.operationId } pub fn operationId(&self) -> &String { &self.operationId }
pub fn revision(&self) -> &i64 { &self.revision } pub fn revision(&self) -> &i64 { &self.revision }
pub fn leaseGeneration(&self) -> &i64 { &self.leaseGeneration }
pub fn outcome(&self) -> &String { &self.outcome } pub fn outcome(&self) -> &String { &self.outcome }
pub fn currentAppliedRevision(&self) -> &i64 { &self.currentAppliedRevision } pub fn currentAppliedRevision(&self) -> &Option<i64> { &self.currentAppliedRevision }
pub fn failureCode(&self) -> &Option<String> { &self.failureCode } pub fn failureCode(&self) -> &Option<String> { &self.failureCode }
} }
@@ -1107,14 +1115,15 @@ pub struct GatewayQualityWork {
reconnectSequence: i64, reconnectSequence: i64,
operationId: String, operationId: String,
revision: i64, revision: i64,
leaseGeneration: i64,
leaseExpiresAt: String, leaseExpiresAt: String,
selectedDescriptor: SelectedSessionDescriptor, selectedDescriptor: SelectedSessionDescriptor,
currentAppliedRevision: Option<i64>, currentAppliedRevision: Option<i64>,
} }
impl GatewayQualityWork { impl GatewayQualityWork {
pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, operationId: String, revision: i64, leaseExpiresAt: String, selectedDescriptor: SelectedSessionDescriptor, currentAppliedRevision: Option<i64>) -> Result<Self, ValidationError> { pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, operationId: String, revision: i64, leaseGeneration: i64, leaseExpiresAt: String, selectedDescriptor: SelectedSessionDescriptor, currentAppliedRevision: Option<i64>) -> Result<Self, ValidationError> {
let value = Self { version, sessionId, gatewayId, reconnectSequence, operationId, revision, leaseExpiresAt, selectedDescriptor, currentAppliedRevision }; let value = Self { version, sessionId, gatewayId, reconnectSequence, operationId, revision, leaseGeneration, leaseExpiresAt, selectedDescriptor, currentAppliedRevision };
value.validate()?; value.validate()?;
Ok(value) Ok(value)
} }
@@ -1132,6 +1141,7 @@ impl GatewayQualityWork {
if self.operationId.len() > 36 { return Err(ValidationError::new("operation_id", "max_length")); } if self.operationId.len() > 36 { return Err(ValidationError::new("operation_id", "max_length")); }
if !valid_canonical_uuid(self.operationId.as_str()) { return Err(ValidationError::new("operation_id", "invalid_uuid")); } if !valid_canonical_uuid(self.operationId.as_str()) { return Err(ValidationError::new("operation_id", "invalid_uuid")); }
if self.revision < 1 { return Err(ValidationError::new("revision", "minimum")); } if self.revision < 1 { return Err(ValidationError::new("revision", "minimum")); }
if self.leaseGeneration < 1 { return Err(ValidationError::new("lease_generation", "minimum")); }
if self.leaseExpiresAt.len() > 64 { return Err(ValidationError::new("lease_expires_at", "max_length")); } if self.leaseExpiresAt.len() > 64 { return Err(ValidationError::new("lease_expires_at", "max_length")); }
if !valid_rfc3339_utc(self.leaseExpiresAt.as_str()) { return Err(ValidationError::new("lease_expires_at", "invalid_time")); } if !valid_rfc3339_utc(self.leaseExpiresAt.as_str()) { return Err(ValidationError::new("lease_expires_at", "invalid_time")); }
self.selectedDescriptor.validate().map_err(|_| ValidationError::new("selected_descriptor", "invalid_object"))?; self.selectedDescriptor.validate().map_err(|_| ValidationError::new("selected_descriptor", "invalid_object"))?;
@@ -1146,6 +1156,7 @@ impl GatewayQualityWork {
pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence } pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence }
pub fn operationId(&self) -> &String { &self.operationId } pub fn operationId(&self) -> &String { &self.operationId }
pub fn revision(&self) -> &i64 { &self.revision } pub fn revision(&self) -> &i64 { &self.revision }
pub fn leaseGeneration(&self) -> &i64 { &self.leaseGeneration }
pub fn leaseExpiresAt(&self) -> &String { &self.leaseExpiresAt } pub fn leaseExpiresAt(&self) -> &String { &self.leaseExpiresAt }
pub fn selectedDescriptor(&self) -> &SelectedSessionDescriptor { &self.selectedDescriptor } pub fn selectedDescriptor(&self) -> &SelectedSessionDescriptor { &self.selectedDescriptor }
pub fn currentAppliedRevision(&self) -> &Option<i64> { &self.currentAppliedRevision } pub fn currentAppliedRevision(&self) -> &Option<i64> { &self.currentAppliedRevision }
@@ -1157,14 +1168,16 @@ pub struct GatewayQualityWorkRequest {
sessionId: String, sessionId: String,
gatewayId: String, gatewayId: String,
reconnectSequence: i64, reconnectSequence: i64,
operationId: String, acquisition: String,
revision: i64, operationId: Option<String>,
revision: Option<i64>,
leaseGeneration: Option<i64>,
currentAppliedRevision: Option<i64>, currentAppliedRevision: Option<i64>,
} }
impl GatewayQualityWorkRequest { impl GatewayQualityWorkRequest {
pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, operationId: String, revision: i64, currentAppliedRevision: Option<i64>) -> Result<Self, ValidationError> { pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, acquisition: String, operationId: Option<String>, revision: Option<i64>, leaseGeneration: Option<i64>, currentAppliedRevision: Option<i64>) -> Result<Self, ValidationError> {
let value = Self { version, sessionId, gatewayId, reconnectSequence, operationId, revision, currentAppliedRevision }; let value = Self { version, sessionId, gatewayId, reconnectSequence, acquisition, operationId, revision, leaseGeneration, currentAppliedRevision };
value.validate()?; value.validate()?;
Ok(value) Ok(value)
} }
@@ -1177,22 +1190,34 @@ impl GatewayQualityWorkRequest {
if !self.gatewayId.is_empty() && self.gatewayId.len() < 1 { return Err(ValidationError::new("gateway_id", "min_length")); } if !self.gatewayId.is_empty() && self.gatewayId.len() < 1 { return Err(ValidationError::new("gateway_id", "min_length")); }
if self.gatewayId.len() > 128 { return Err(ValidationError::new("gateway_id", "max_length")); } if self.gatewayId.len() > 128 { return Err(ValidationError::new("gateway_id", "max_length")); }
if self.reconnectSequence < 0 { return Err(ValidationError::new("reconnect_sequence", "minimum")); } if self.reconnectSequence < 0 { return Err(ValidationError::new("reconnect_sequence", "minimum")); }
if self.operationId.is_empty() { return Err(ValidationError::new("operation_id", "required")); } if self.acquisition != "poll" && self.acquisition != "prompt" && self.acquisition != "observation" { return Err(ValidationError::new("acquisition", "invalid_value")); }
if !self.operationId.is_empty() && self.operationId.len() < 36 { return Err(ValidationError::new("operation_id", "min_length")); } if let Some(value) = &self.operationId {
if self.operationId.len() > 36 { return Err(ValidationError::new("operation_id", "max_length")); } if !value.is_empty() && value.len() < 36 { return Err(ValidationError::new("operation_id", "min_length")); }
if !valid_canonical_uuid(self.operationId.as_str()) { return Err(ValidationError::new("operation_id", "invalid_uuid")); } if value.len() > 36 { return Err(ValidationError::new("operation_id", "max_length")); }
if self.revision < 1 { return Err(ValidationError::new("revision", "minimum")); } if !valid_canonical_uuid(value.as_str()) { return Err(ValidationError::new("operation_id", "invalid_uuid")); }
}
if let Some(value) = &self.revision {
if *value < 1 { return Err(ValidationError::new("revision", "minimum")); }
}
if let Some(value) = &self.leaseGeneration {
if *value < 1 { return Err(ValidationError::new("lease_generation", "minimum")); }
}
if let Some(value) = &self.currentAppliedRevision { if let Some(value) = &self.currentAppliedRevision {
if *value < 0 { return Err(ValidationError::new("current_applied_revision", "minimum")); } if *value < 0 { return Err(ValidationError::new("current_applied_revision", "minimum")); }
} }
if self.acquisition == "poll" && (self.operationId.is_some() || self.revision.is_some() || self.leaseGeneration.is_some() || self.currentAppliedRevision.is_some()) { return Err(ValidationError::new("acquisition", "invalid_tagged_value")); }
if self.acquisition == "prompt" && (self.operationId.is_none() || self.revision.is_none() || self.leaseGeneration.is_some() || self.currentAppliedRevision.is_some()) { return Err(ValidationError::new("acquisition", "invalid_tagged_value")); }
if self.acquisition == "observation" && (self.operationId.is_none() || self.revision.is_none() || self.leaseGeneration.is_none() || self.currentAppliedRevision.is_none()) { return Err(ValidationError::new("acquisition", "invalid_tagged_value")); }
Ok(()) Ok(())
} }
pub fn version(&self) -> &String { &self.version } pub fn version(&self) -> &String { &self.version }
pub fn sessionId(&self) -> &String { &self.sessionId } pub fn sessionId(&self) -> &String { &self.sessionId }
pub fn gatewayId(&self) -> &String { &self.gatewayId } pub fn gatewayId(&self) -> &String { &self.gatewayId }
pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence } pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence }
pub fn operationId(&self) -> &String { &self.operationId } pub fn acquisition(&self) -> &String { &self.acquisition }
pub fn revision(&self) -> &i64 { &self.revision } pub fn operationId(&self) -> &Option<String> { &self.operationId }
pub fn revision(&self) -> &Option<i64> { &self.revision }
pub fn leaseGeneration(&self) -> &Option<i64> { &self.leaseGeneration }
pub fn currentAppliedRevision(&self) -> &Option<i64> { &self.currentAppliedRevision } pub fn currentAppliedRevision(&self) -> &Option<i64> { &self.currentAppliedRevision }
} }
@@ -1362,12 +1387,13 @@ pub struct GatewayStopWorkRequest {
sessionId: String, sessionId: String,
gatewayId: String, gatewayId: String,
reconnectSequence: i64, reconnectSequence: i64,
operationId: String, acquisition: String,
operationId: Option<String>,
} }
impl GatewayStopWorkRequest { impl GatewayStopWorkRequest {
pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, operationId: String) -> Result<Self, ValidationError> { pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, acquisition: String, operationId: Option<String>) -> Result<Self, ValidationError> {
let value = Self { version, sessionId, gatewayId, reconnectSequence, operationId }; let value = Self { version, sessionId, gatewayId, reconnectSequence, acquisition, operationId };
value.validate()?; value.validate()?;
Ok(value) Ok(value)
} }
@@ -1380,17 +1406,21 @@ impl GatewayStopWorkRequest {
if !self.gatewayId.is_empty() && self.gatewayId.len() < 1 { return Err(ValidationError::new("gateway_id", "min_length")); } if !self.gatewayId.is_empty() && self.gatewayId.len() < 1 { return Err(ValidationError::new("gateway_id", "min_length")); }
if self.gatewayId.len() > 128 { return Err(ValidationError::new("gateway_id", "max_length")); } if self.gatewayId.len() > 128 { return Err(ValidationError::new("gateway_id", "max_length")); }
if self.reconnectSequence < 0 { return Err(ValidationError::new("reconnect_sequence", "minimum")); } if self.reconnectSequence < 0 { return Err(ValidationError::new("reconnect_sequence", "minimum")); }
if self.operationId.is_empty() { return Err(ValidationError::new("operation_id", "required")); } if self.acquisition != "poll" && self.acquisition != "prompt" { return Err(ValidationError::new("acquisition", "invalid_value")); }
if !self.operationId.is_empty() && self.operationId.len() < 36 { return Err(ValidationError::new("operation_id", "min_length")); } if let Some(value) = &self.operationId {
if self.operationId.len() > 36 { return Err(ValidationError::new("operation_id", "max_length")); } if !value.is_empty() && value.len() < 36 { return Err(ValidationError::new("operation_id", "min_length")); }
if !valid_canonical_uuid(self.operationId.as_str()) { return Err(ValidationError::new("operation_id", "invalid_uuid")); } if value.len() > 36 { return Err(ValidationError::new("operation_id", "max_length")); }
if !valid_canonical_uuid(value.as_str()) { return Err(ValidationError::new("operation_id", "invalid_uuid")); }
}
if self.acquisition == "poll" && self.operationId.is_some() || self.acquisition == "prompt" && self.operationId.is_none() { return Err(ValidationError::new("acquisition", "invalid_tagged_value")); }
Ok(()) Ok(())
} }
pub fn version(&self) -> &String { &self.version } pub fn version(&self) -> &String { &self.version }
pub fn sessionId(&self) -> &String { &self.sessionId } pub fn sessionId(&self) -> &String { &self.sessionId }
pub fn gatewayId(&self) -> &String { &self.gatewayId } pub fn gatewayId(&self) -> &String { &self.gatewayId }
pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence } pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence }
pub fn operationId(&self) -> &String { &self.operationId } pub fn acquisition(&self) -> &String { &self.acquisition }
pub fn operationId(&self) -> &Option<String> { &self.operationId }
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
+65 -28
View File
@@ -1,13 +1,14 @@
// Code generated by tools/generate.py; DO NOT EDIT. // Code generated by tools/generate.py; DO NOT EDIT.
import Foundation import Foundation
public typealias JSONObject = [String: String] public typealias JSONObject = [String: String]
public let schemaSHA256 = "614fa11dd1f49b8e10bf21b8c10eadbc1468d16bfcc6ed0a26d29671a5e61300" public let schemaSHA256 = "8c1ed430127cf5774a919f14ff4770b2509d322c8518bf30c412e1e89eeffced"
public let currentWireVersion = "2" public let currentWireVersion = "2"
public let nMinus1WireVersion = "1" public let nMinus1WireVersion = "1"
public let nMinus2WireVersion = "0" public let nMinus2WireVersion = "0"
public struct ContractValidationError: Error, Equatable { public let field: String; public let code: String } 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 } } 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 } }
private func rejectDuplicateJSONKeys(_ data: Data) throws { private func rejectDuplicateJSONKeys(_ data: Data) throws {
guard data.count <= 1_048_576 else { throw ContractValidationError(field: "json", code: "payload_too_large") }
var index = 0 var index = 0
func skipWhitespace() { while index < data.count && [9, 10, 13, 32].contains(data[index]) { index += 1 } } func skipWhitespace() { while index < data.count && [9, 10, 13, 32].contains(data[index]) { index += 1 } }
func parseString() throws -> String { func parseString() throws -> String {
@@ -21,7 +22,8 @@ private func rejectDuplicateJSONKeys(_ data: Data) throws {
} }
throw ContractValidationError(field: "json", code: "invalid_json") throw ContractValidationError(field: "json", code: "invalid_json")
} }
func parseValue() throws { func parseValue(_ depth: Int) throws {
guard depth <= 64 else { throw ContractValidationError(field: "json", code: "nesting_too_deep") }
skipWhitespace() skipWhitespace()
guard index < data.count else { throw ContractValidationError(field: "json", code: "invalid_json") } guard index < data.count else { throw ContractValidationError(field: "json", code: "invalid_json") }
if data[index] == 123 { if data[index] == 123 {
@@ -36,7 +38,7 @@ private func rejectDuplicateJSONKeys(_ data: Data) throws {
skipWhitespace() skipWhitespace()
guard index < data.count, data[index] == 58 else { throw ContractValidationError(field: "json", code: "invalid_json") } guard index < data.count, data[index] == 58 else { throw ContractValidationError(field: "json", code: "invalid_json") }
index += 1 index += 1
try parseValue() try parseValue(depth + 1)
skipWhitespace() skipWhitespace()
guard index < data.count else { throw ContractValidationError(field: "json", code: "invalid_json") } guard index < data.count else { throw ContractValidationError(field: "json", code: "invalid_json") }
if data[index] == 125 { index += 1; return } if data[index] == 125 { index += 1; return }
@@ -49,7 +51,7 @@ private func rejectDuplicateJSONKeys(_ data: Data) throws {
skipWhitespace() skipWhitespace()
if index < data.count, data[index] == 93 { index += 1; return } if index < data.count, data[index] == 93 { index += 1; return }
while true { while true {
try parseValue() try parseValue(depth + 1)
skipWhitespace() skipWhitespace()
guard index < data.count else { throw ContractValidationError(field: "json", code: "invalid_json") } guard index < data.count else { throw ContractValidationError(field: "json", code: "invalid_json") }
if data[index] == 93 { index += 1; return } if data[index] == 93 { index += 1; return }
@@ -62,7 +64,7 @@ private func rejectDuplicateJSONKeys(_ data: Data) throws {
while index < data.count && ![9, 10, 13, 32, 44, 93, 125].contains(data[index]) { index += 1 } while index < data.count && ![9, 10, 13, 32, 44, 93, 125].contains(data[index]) { index += 1 }
guard index > start else { throw ContractValidationError(field: "json", code: "invalid_json") } guard index > start else { throw ContractValidationError(field: "json", code: "invalid_json") }
} }
try parseValue() try parseValue(0)
skipWhitespace() skipWhitespace()
guard index == data.count else { throw ContractValidationError(field: "json", code: "trailing_json") } guard index == data.count else { throw ContractValidationError(field: "json", code: "trailing_json") }
} }
@@ -1434,8 +1436,9 @@ public struct GatewayQualityAck: Codable, Equatable {
public let reconnectSequence: Int64 public let reconnectSequence: Int64
public let operationId: String public let operationId: String
public let revision: Int64 public let revision: Int64
public let leaseGeneration: Int64
public let outcome: String public let outcome: String
public let currentAppliedRevision: Int64 public let currentAppliedRevision: Int64?
public let failureCode: String? public let failureCode: String?
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case version = "version" case version = "version"
@@ -1444,18 +1447,20 @@ public struct GatewayQualityAck: Codable, Equatable {
case reconnectSequence = "reconnect_sequence" case reconnectSequence = "reconnect_sequence"
case operationId = "operation_id" case operationId = "operation_id"
case revision = "revision" case revision = "revision"
case leaseGeneration = "lease_generation"
case outcome = "outcome" case outcome = "outcome"
case currentAppliedRevision = "current_applied_revision" case currentAppliedRevision = "current_applied_revision"
case failureCode = "failure_code" case failureCode = "failure_code"
} }
public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, operationId: String, revision: Int64, outcome: String, currentAppliedRevision: Int64, failureCode: String?) throws { public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, operationId: String, revision: Int64, leaseGeneration: Int64, outcome: String, currentAppliedRevision: Int64?, failureCode: String?) throws {
self.version = version self.version = version
self.sessionId = sessionId self.sessionId = sessionId
self.gatewayId = gatewayId self.gatewayId = gatewayId
self.reconnectSequence = reconnectSequence self.reconnectSequence = reconnectSequence
self.operationId = operationId self.operationId = operationId
self.revision = revision self.revision = revision
self.leaseGeneration = leaseGeneration
self.outcome = outcome self.outcome = outcome
self.currentAppliedRevision = currentAppliedRevision self.currentAppliedRevision = currentAppliedRevision
self.failureCode = failureCode self.failureCode = failureCode
@@ -1466,7 +1471,7 @@ public struct GatewayQualityAck: Codable, Equatable {
let all = try decoder.container(keyedBy: AnyCodingKey.self) let all = try decoder.container(keyedBy: AnyCodingKey.self)
for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") } for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") }
let c = try decoder.container(keyedBy: CodingKeys.self) let c = try decoder.container(keyedBy: CodingKeys.self)
try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), operationId: try c.decode(String.self, forKey: .operationId), revision: try c.decode(Int64.self, forKey: .revision), outcome: try c.decode(String.self, forKey: .outcome), currentAppliedRevision: try c.decode(Int64.self, forKey: .currentAppliedRevision), failureCode: try c.decodeIfPresent(String.self, forKey: .failureCode)) try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), operationId: try c.decode(String.self, forKey: .operationId), revision: try c.decode(Int64.self, forKey: .revision), leaseGeneration: try c.decode(Int64.self, forKey: .leaseGeneration), outcome: try c.decode(String.self, forKey: .outcome), currentAppliedRevision: try c.decodeIfPresent(Int64.self, forKey: .currentAppliedRevision), failureCode: try c.decodeIfPresent(String.self, forKey: .failureCode))
} }
public func validate() throws { public func validate() throws {
@@ -1483,12 +1488,18 @@ public struct GatewayQualityAck: Codable, Equatable {
if self.operationId.utf8.count > 36 { throw ContractValidationError(field: "operation_id", code: "max_length") } if self.operationId.utf8.count > 36 { throw ContractValidationError(field: "operation_id", code: "max_length") }
if !validCanonicalUUID(self.operationId) { throw ContractValidationError(field: "operation_id", code: "invalid_uuid") } if !validCanonicalUUID(self.operationId) { throw ContractValidationError(field: "operation_id", code: "invalid_uuid") }
if self.revision < 1 { throw ContractValidationError(field: "revision", code: "minimum") } if self.revision < 1 { throw ContractValidationError(field: "revision", code: "minimum") }
if !["applied", "not_applied", "uncertain"].contains(self.outcome) { throw ContractValidationError(field: "outcome", code: "invalid_value") } if self.leaseGeneration < 1 { throw ContractValidationError(field: "lease_generation", code: "minimum") }
if self.currentAppliedRevision < 0 { throw ContractValidationError(field: "current_applied_revision", code: "minimum") } if !["applied", "proven_prior", "unknown"].contains(self.outcome) { throw ContractValidationError(field: "outcome", code: "invalid_value") }
if let value = self.currentAppliedRevision {
if value < 0 { throw ContractValidationError(field: "current_applied_revision", code: "minimum") }
}
if let value = self.failureCode { if let value = self.failureCode {
if !value.isEmpty && value.utf8.count < 1 { throw ContractValidationError(field: "failure_code", code: "min_length") } if !value.isEmpty && value.utf8.count < 1 { throw ContractValidationError(field: "failure_code", code: "min_length") }
if value.utf8.count > 128 { throw ContractValidationError(field: "failure_code", code: "max_length") } if value.utf8.count > 128 { throw ContractValidationError(field: "failure_code", code: "max_length") }
} }
if outcome == "applied" && currentAppliedRevision != revision { throw ContractValidationError(field: "current_applied_revision", code: "invalid_tagged_value") }
if outcome == "proven_prior" && (currentAppliedRevision == nil || currentAppliedRevision! >= revision) { throw ContractValidationError(field: "current_applied_revision", code: "invalid_tagged_value") }
if outcome == "unknown" && currentAppliedRevision != nil { throw ContractValidationError(field: "current_applied_revision", code: "invalid_tagged_value") }
} }
public static func decodeJSON(_ data: Data) throws -> Self { try rejectDuplicateJSONKeys(data); return try JSONDecoder().decode(Self.self, from: data) } public static func decodeJSON(_ data: Data) throws -> Self { try rejectDuplicateJSONKeys(data); return try JSONDecoder().decode(Self.self, from: data) }
@@ -1502,6 +1513,7 @@ public struct GatewayQualityWork: Codable, Equatable {
public let reconnectSequence: Int64 public let reconnectSequence: Int64
public let operationId: String public let operationId: String
public let revision: Int64 public let revision: Int64
public let leaseGeneration: Int64
public let leaseExpiresAt: String public let leaseExpiresAt: String
public let selectedDescriptor: SelectedSessionDescriptor public let selectedDescriptor: SelectedSessionDescriptor
public let currentAppliedRevision: Int64? public let currentAppliedRevision: Int64?
@@ -1512,18 +1524,20 @@ public struct GatewayQualityWork: Codable, Equatable {
case reconnectSequence = "reconnect_sequence" case reconnectSequence = "reconnect_sequence"
case operationId = "operation_id" case operationId = "operation_id"
case revision = "revision" case revision = "revision"
case leaseGeneration = "lease_generation"
case leaseExpiresAt = "lease_expires_at" case leaseExpiresAt = "lease_expires_at"
case selectedDescriptor = "selected_descriptor" case selectedDescriptor = "selected_descriptor"
case currentAppliedRevision = "current_applied_revision" case currentAppliedRevision = "current_applied_revision"
} }
public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, operationId: String, revision: Int64, leaseExpiresAt: String, selectedDescriptor: SelectedSessionDescriptor, currentAppliedRevision: Int64?) throws { public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, operationId: String, revision: Int64, leaseGeneration: Int64, leaseExpiresAt: String, selectedDescriptor: SelectedSessionDescriptor, currentAppliedRevision: Int64?) throws {
self.version = version self.version = version
self.sessionId = sessionId self.sessionId = sessionId
self.gatewayId = gatewayId self.gatewayId = gatewayId
self.reconnectSequence = reconnectSequence self.reconnectSequence = reconnectSequence
self.operationId = operationId self.operationId = operationId
self.revision = revision self.revision = revision
self.leaseGeneration = leaseGeneration
self.leaseExpiresAt = leaseExpiresAt self.leaseExpiresAt = leaseExpiresAt
self.selectedDescriptor = selectedDescriptor self.selectedDescriptor = selectedDescriptor
self.currentAppliedRevision = currentAppliedRevision self.currentAppliedRevision = currentAppliedRevision
@@ -1534,7 +1548,7 @@ public struct GatewayQualityWork: Codable, Equatable {
let all = try decoder.container(keyedBy: AnyCodingKey.self) let all = try decoder.container(keyedBy: AnyCodingKey.self)
for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") } for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") }
let c = try decoder.container(keyedBy: CodingKeys.self) let c = try decoder.container(keyedBy: CodingKeys.self)
try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), operationId: try c.decode(String.self, forKey: .operationId), revision: try c.decode(Int64.self, forKey: .revision), leaseExpiresAt: try c.decode(String.self, forKey: .leaseExpiresAt), selectedDescriptor: try c.decode(SelectedSessionDescriptor.self, forKey: .selectedDescriptor), currentAppliedRevision: try c.decodeIfPresent(Int64.self, forKey: .currentAppliedRevision)) try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), operationId: try c.decode(String.self, forKey: .operationId), revision: try c.decode(Int64.self, forKey: .revision), leaseGeneration: try c.decode(Int64.self, forKey: .leaseGeneration), leaseExpiresAt: try c.decode(String.self, forKey: .leaseExpiresAt), selectedDescriptor: try c.decode(SelectedSessionDescriptor.self, forKey: .selectedDescriptor), currentAppliedRevision: try c.decodeIfPresent(Int64.self, forKey: .currentAppliedRevision))
} }
public func validate() throws { public func validate() throws {
@@ -1551,6 +1565,7 @@ public struct GatewayQualityWork: Codable, Equatable {
if self.operationId.utf8.count > 36 { throw ContractValidationError(field: "operation_id", code: "max_length") } if self.operationId.utf8.count > 36 { throw ContractValidationError(field: "operation_id", code: "max_length") }
if !validCanonicalUUID(self.operationId) { throw ContractValidationError(field: "operation_id", code: "invalid_uuid") } if !validCanonicalUUID(self.operationId) { throw ContractValidationError(field: "operation_id", code: "invalid_uuid") }
if self.revision < 1 { throw ContractValidationError(field: "revision", code: "minimum") } if self.revision < 1 { throw ContractValidationError(field: "revision", code: "minimum") }
if self.leaseGeneration < 1 { throw ContractValidationError(field: "lease_generation", code: "minimum") }
if self.leaseExpiresAt.utf8.count > 64 { throw ContractValidationError(field: "lease_expires_at", code: "max_length") } if self.leaseExpiresAt.utf8.count > 64 { throw ContractValidationError(field: "lease_expires_at", code: "max_length") }
if !validRFC3339UTC(self.leaseExpiresAt) { throw ContractValidationError(field: "lease_expires_at", code: "invalid_time") } if !validRFC3339UTC(self.leaseExpiresAt) { throw ContractValidationError(field: "lease_expires_at", code: "invalid_time") }
try self.selectedDescriptor.validate() try self.selectedDescriptor.validate()
@@ -1568,26 +1583,32 @@ public struct GatewayQualityWorkRequest: Codable, Equatable {
public let sessionId: String public let sessionId: String
public let gatewayId: String public let gatewayId: String
public let reconnectSequence: Int64 public let reconnectSequence: Int64
public let operationId: String public let acquisition: String
public let revision: Int64 public let operationId: String?
public let revision: Int64?
public let leaseGeneration: Int64?
public let currentAppliedRevision: Int64? public let currentAppliedRevision: Int64?
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case version = "version" case version = "version"
case sessionId = "session_id" case sessionId = "session_id"
case gatewayId = "gateway_id" case gatewayId = "gateway_id"
case reconnectSequence = "reconnect_sequence" case reconnectSequence = "reconnect_sequence"
case acquisition = "acquisition"
case operationId = "operation_id" case operationId = "operation_id"
case revision = "revision" case revision = "revision"
case leaseGeneration = "lease_generation"
case currentAppliedRevision = "current_applied_revision" case currentAppliedRevision = "current_applied_revision"
} }
public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, operationId: String, revision: Int64, currentAppliedRevision: Int64?) throws { public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, acquisition: String, operationId: String?, revision: Int64?, leaseGeneration: Int64?, currentAppliedRevision: Int64?) throws {
self.version = version self.version = version
self.sessionId = sessionId self.sessionId = sessionId
self.gatewayId = gatewayId self.gatewayId = gatewayId
self.reconnectSequence = reconnectSequence self.reconnectSequence = reconnectSequence
self.acquisition = acquisition
self.operationId = operationId self.operationId = operationId
self.revision = revision self.revision = revision
self.leaseGeneration = leaseGeneration
self.currentAppliedRevision = currentAppliedRevision self.currentAppliedRevision = currentAppliedRevision
try validate() try validate()
} }
@@ -1596,7 +1617,7 @@ public struct GatewayQualityWorkRequest: Codable, Equatable {
let all = try decoder.container(keyedBy: AnyCodingKey.self) let all = try decoder.container(keyedBy: AnyCodingKey.self)
for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") } for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") }
let c = try decoder.container(keyedBy: CodingKeys.self) let c = try decoder.container(keyedBy: CodingKeys.self)
try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), operationId: try c.decode(String.self, forKey: .operationId), revision: try c.decode(Int64.self, forKey: .revision), currentAppliedRevision: try c.decodeIfPresent(Int64.self, forKey: .currentAppliedRevision)) try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), acquisition: try c.decode(String.self, forKey: .acquisition), operationId: try c.decodeIfPresent(String.self, forKey: .operationId), revision: try c.decodeIfPresent(Int64.self, forKey: .revision), leaseGeneration: try c.decodeIfPresent(Int64.self, forKey: .leaseGeneration), currentAppliedRevision: try c.decodeIfPresent(Int64.self, forKey: .currentAppliedRevision))
} }
public func validate() throws { public func validate() throws {
@@ -1608,14 +1629,24 @@ public struct GatewayQualityWorkRequest: Codable, Equatable {
if !self.gatewayId.isEmpty && self.gatewayId.utf8.count < 1 { throw ContractValidationError(field: "gateway_id", code: "min_length") } if !self.gatewayId.isEmpty && self.gatewayId.utf8.count < 1 { throw ContractValidationError(field: "gateway_id", code: "min_length") }
if self.gatewayId.utf8.count > 128 { throw ContractValidationError(field: "gateway_id", code: "max_length") } if self.gatewayId.utf8.count > 128 { throw ContractValidationError(field: "gateway_id", code: "max_length") }
if self.reconnectSequence < 0 { throw ContractValidationError(field: "reconnect_sequence", code: "minimum") } if self.reconnectSequence < 0 { throw ContractValidationError(field: "reconnect_sequence", code: "minimum") }
if self.operationId.isEmpty { throw ContractValidationError(field: "operation_id", code: "required") } if !["poll", "prompt", "observation"].contains(self.acquisition) { throw ContractValidationError(field: "acquisition", code: "invalid_value") }
if !self.operationId.isEmpty && self.operationId.utf8.count < 36 { throw ContractValidationError(field: "operation_id", code: "min_length") } if let value = self.operationId {
if self.operationId.utf8.count > 36 { throw ContractValidationError(field: "operation_id", code: "max_length") } if !value.isEmpty && value.utf8.count < 36 { throw ContractValidationError(field: "operation_id", code: "min_length") }
if !validCanonicalUUID(self.operationId) { throw ContractValidationError(field: "operation_id", code: "invalid_uuid") } if value.utf8.count > 36 { throw ContractValidationError(field: "operation_id", code: "max_length") }
if self.revision < 1 { throw ContractValidationError(field: "revision", code: "minimum") } if !validCanonicalUUID(value) { throw ContractValidationError(field: "operation_id", code: "invalid_uuid") }
}
if let value = self.revision {
if value < 1 { throw ContractValidationError(field: "revision", code: "minimum") }
}
if let value = self.leaseGeneration {
if value < 1 { throw ContractValidationError(field: "lease_generation", code: "minimum") }
}
if let value = self.currentAppliedRevision { if let value = self.currentAppliedRevision {
if value < 0 { throw ContractValidationError(field: "current_applied_revision", code: "minimum") } if value < 0 { throw ContractValidationError(field: "current_applied_revision", code: "minimum") }
} }
if acquisition == "poll" && (operationId != nil || revision != nil || leaseGeneration != nil || currentAppliedRevision != nil) { throw ContractValidationError(field: "acquisition", code: "invalid_tagged_value") }
if acquisition == "prompt" && (operationId == nil || revision == nil || leaseGeneration != nil || currentAppliedRevision != nil) { throw ContractValidationError(field: "acquisition", code: "invalid_tagged_value") }
if acquisition == "observation" && (operationId == nil || revision == nil || leaseGeneration == nil || currentAppliedRevision == nil) { throw ContractValidationError(field: "acquisition", code: "invalid_tagged_value") }
} }
public static func decodeJSON(_ data: Data) throws -> Self { try rejectDuplicateJSONKeys(data); return try JSONDecoder().decode(Self.self, from: data) } public static func decodeJSON(_ data: Data) throws -> Self { try rejectDuplicateJSONKeys(data); return try JSONDecoder().decode(Self.self, from: data) }
@@ -1834,20 +1865,23 @@ public struct GatewayStopWorkRequest: Codable, Equatable {
public let sessionId: String public let sessionId: String
public let gatewayId: String public let gatewayId: String
public let reconnectSequence: Int64 public let reconnectSequence: Int64
public let operationId: String public let acquisition: String
public let operationId: String?
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case version = "version" case version = "version"
case sessionId = "session_id" case sessionId = "session_id"
case gatewayId = "gateway_id" case gatewayId = "gateway_id"
case reconnectSequence = "reconnect_sequence" case reconnectSequence = "reconnect_sequence"
case acquisition = "acquisition"
case operationId = "operation_id" case operationId = "operation_id"
} }
public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, operationId: String) throws { public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, acquisition: String, operationId: String?) throws {
self.version = version self.version = version
self.sessionId = sessionId self.sessionId = sessionId
self.gatewayId = gatewayId self.gatewayId = gatewayId
self.reconnectSequence = reconnectSequence self.reconnectSequence = reconnectSequence
self.acquisition = acquisition
self.operationId = operationId self.operationId = operationId
try validate() try validate()
} }
@@ -1856,7 +1890,7 @@ public struct GatewayStopWorkRequest: Codable, Equatable {
let all = try decoder.container(keyedBy: AnyCodingKey.self) let all = try decoder.container(keyedBy: AnyCodingKey.self)
for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") } for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") }
let c = try decoder.container(keyedBy: CodingKeys.self) let c = try decoder.container(keyedBy: CodingKeys.self)
try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), operationId: try c.decode(String.self, forKey: .operationId)) try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), acquisition: try c.decode(String.self, forKey: .acquisition), operationId: try c.decodeIfPresent(String.self, forKey: .operationId))
} }
public func validate() throws { public func validate() throws {
@@ -1868,10 +1902,13 @@ public struct GatewayStopWorkRequest: Codable, Equatable {
if !self.gatewayId.isEmpty && self.gatewayId.utf8.count < 1 { throw ContractValidationError(field: "gateway_id", code: "min_length") } if !self.gatewayId.isEmpty && self.gatewayId.utf8.count < 1 { throw ContractValidationError(field: "gateway_id", code: "min_length") }
if self.gatewayId.utf8.count > 128 { throw ContractValidationError(field: "gateway_id", code: "max_length") } if self.gatewayId.utf8.count > 128 { throw ContractValidationError(field: "gateway_id", code: "max_length") }
if self.reconnectSequence < 0 { throw ContractValidationError(field: "reconnect_sequence", code: "minimum") } if self.reconnectSequence < 0 { throw ContractValidationError(field: "reconnect_sequence", code: "minimum") }
if self.operationId.isEmpty { throw ContractValidationError(field: "operation_id", code: "required") } if !["poll", "prompt"].contains(self.acquisition) { throw ContractValidationError(field: "acquisition", code: "invalid_value") }
if !self.operationId.isEmpty && self.operationId.utf8.count < 36 { throw ContractValidationError(field: "operation_id", code: "min_length") } if let value = self.operationId {
if self.operationId.utf8.count > 36 { throw ContractValidationError(field: "operation_id", code: "max_length") } if !value.isEmpty && value.utf8.count < 36 { throw ContractValidationError(field: "operation_id", code: "min_length") }
if !validCanonicalUUID(self.operationId) { throw ContractValidationError(field: "operation_id", code: "invalid_uuid") } if value.utf8.count > 36 { throw ContractValidationError(field: "operation_id", code: "max_length") }
if !validCanonicalUUID(value) { throw ContractValidationError(field: "operation_id", code: "invalid_uuid") }
}
if acquisition == "poll" && operationId != nil || acquisition == "prompt" && operationId == nil { throw ContractValidationError(field: "acquisition", code: "invalid_tagged_value") }
} }
public static func decodeJSON(_ data: Data) throws -> Self { try rejectDuplicateJSONKeys(data); return try JSONDecoder().decode(Self.self, from: data) } public static func decodeJSON(_ data: Data) throws -> Self { try rejectDuplicateJSONKeys(data); return try JSONDecoder().decode(Self.self, from: data) }
+26 -18
View File
@@ -334,7 +334,7 @@ paths:
operationId: getSessionQualityLimits operationId: getSessionQualityLimits
security: [{nativeBearer: []}] security: [{nativeBearer: []}]
responses: responses:
'200': {description: Current global native session quality limits., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/SessionQualityLimits}}}} '200': {description: 'Current global native session quality limits. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/SessionQualityLimits}}}}
'401': {$ref: '#/components/responses/Unauthorized'} '401': {$ref: '#/components/responses/Unauthorized'}
/api/v1/session-quality-limits/assignments/{assignment_id}: /api/v1/session-quality-limits/assignments/{assignment_id}:
get: get:
@@ -343,7 +343,7 @@ paths:
security: [{nativeBearer: []}] security: [{nativeBearer: []}]
parameters: [{$ref: '#/components/parameters/AssignmentID'}] parameters: [{$ref: '#/components/parameters/AssignmentID'}]
responses: responses:
'200': {description: Current assignment-specific native session quality limits., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/SessionQualityLimits}}}} '200': {description: 'Current assignment-specific native session quality limits. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/SessionQualityLimits}}}}
'401': {$ref: '#/components/responses/Unauthorized'} '401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'} '404': {$ref: '#/components/responses/NotFound'}
/api/v1/session-quality-limits/pools/{pool_id}: /api/v1/session-quality-limits/pools/{pool_id}:
@@ -353,7 +353,7 @@ paths:
security: [{nativeBearer: []}] security: [{nativeBearer: []}]
parameters: [{$ref: '#/components/parameters/PoolID'}] parameters: [{$ref: '#/components/parameters/PoolID'}]
responses: responses:
'200': {description: Current pool-specific native session quality limits., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/SessionQualityLimits}}}} '200': {description: 'Current pool-specific native session quality limits. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/SessionQualityLimits}}}}
'401': {$ref: '#/components/responses/Unauthorized'} '401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'} '404': {$ref: '#/components/responses/NotFound'}
/api/v1/admin/entitlements/{entitlement_id}/display-limit-override: /api/v1/admin/entitlements/{entitlement_id}/display-limit-override:
@@ -365,7 +365,7 @@ paths:
browserCsrfCookie: [] browserCsrfCookie: []
browserCsrfHeader: [] browserCsrfHeader: []
parameters: [{$ref: '#/components/parameters/EntitlementID'}] parameters: [{$ref: '#/components/parameters/EntitlementID'}]
requestBody: {required: true, content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/DisplayLimitOverrideRequest}}}} requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/DisplayLimitOverrideRequest}}}}
responses: responses:
'204': {description: Direct entitlement display override updated.} '204': {description: Direct entitlement display override updated.}
'400': {$ref: '#/components/responses/InvalidRequest'} '400': {$ref: '#/components/responses/InvalidRequest'}
@@ -377,12 +377,13 @@ paths:
post: post:
tags: [session-quality] tags: [session-quality]
operationId: createSessionQualityChange operationId: createSessionQualityChange
description: The owning principal and active device/key MUST match the subject-scoped broker session. Maximum JSON body: 16384 bytes.
security: [{nativeBearer: []}] security: [{nativeBearer: []}]
parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/IdempotencyKey'}] parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody: {required: true, content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeRequest}}}} requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeRequest}}}}
responses: responses:
'201': {description: Durable quality change created., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeOperation}}}} '201': {description: 'Durable quality change created. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeOperation}}}}
'200': {description: Existing idempotent quality change returned., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeOperation}}}} '200': {description: 'Existing idempotent quality change returned. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeOperation}}}}
'400': {$ref: '#/components/responses/InvalidRequest'} '400': {$ref: '#/components/responses/InvalidRequest'}
'401': {$ref: '#/components/responses/Unauthorized'} '401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'} '404': {$ref: '#/components/responses/NotFound'}
@@ -391,21 +392,23 @@ paths:
get: get:
tags: [session-quality] tags: [session-quality]
operationId: getSessionQualityChange operationId: getSessionQualityChange
description: The owning principal and active device/key MUST match the subject-scoped broker session and operation. Maximum JSON body: 16384 bytes.
security: [{nativeBearer: []}] security: [{nativeBearer: []}]
parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/OperationID'}] parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/OperationID'}]
responses: responses:
'200': {description: Durable quality change state., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeOperation}}}} '200': {description: 'Durable quality change state. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeOperation}}}}
'401': {$ref: '#/components/responses/Unauthorized'} '401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'} '404': {$ref: '#/components/responses/NotFound'}
/api/v1/broker/sessions/{session_id}/stop-operations: /api/v1/broker/sessions/{session_id}/stop-operations:
post: post:
tags: [session-quality] tags: [session-quality]
operationId: createSessionStopOperation operationId: createSessionStopOperation
description: The owning principal and active device/key MUST match the subject-scoped broker session. Maximum JSON body: 16384 bytes.
security: [{nativeBearer: []}] security: [{nativeBearer: []}]
parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/IdempotencyKey'}] parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/IdempotencyKey'}]
responses: responses:
'201': {description: Durable Stop operation created., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/StopOperation}}}} '201': {description: 'Durable Stop operation created. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/StopOperation}}}}
'200': {description: Existing idempotent Stop operation returned., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/StopOperation}}}} '200': {description: 'Existing idempotent Stop operation returned. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/StopOperation}}}}
'401': {$ref: '#/components/responses/Unauthorized'} '401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'} '404': {$ref: '#/components/responses/NotFound'}
'409': {$ref: '#/components/responses/Conflict'} '409': {$ref: '#/components/responses/Conflict'}
@@ -413,20 +416,22 @@ paths:
get: get:
tags: [session-quality] tags: [session-quality]
operationId: getSessionStopOperation operationId: getSessionStopOperation
description: The owning principal and active device/key MUST match the subject-scoped broker session and operation. Maximum JSON body: 16384 bytes.
security: [{nativeBearer: []}] security: [{nativeBearer: []}]
parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/OperationID'}] parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/OperationID'}]
responses: responses:
'200': {description: Durable Stop operation state., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/StopOperation}}}} '200': {description: 'Durable Stop operation state. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/StopOperation}}}}
'401': {$ref: '#/components/responses/Unauthorized'} '401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'} '404': {$ref: '#/components/responses/NotFound'}
/api/v1/gateway/quality-work: /api/v1/gateway/quality-work:
post: post:
tags: [gateway] tags: [gateway]
operationId: acquireGatewayQualityWork operationId: acquireGatewayQualityWork
description: The authenticated gateway mTLS certificate identity MUST match `gateway_id`; Server authority MUST match the session, gateway, reconnect sequence, and supplied operation/revision/lease generation coordinates. A `poll` acquisition omits unknown operation coordinates and discovers pending session work after a lost prompt; `prompt` and `observation` coordinates MUST match exactly. Maximum JSON body: 16384 bytes.
security: [{gatewayMutualTLS: []}] security: [{gatewayMutualTLS: []}]
requestBody: {required: true, content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayQualityWorkRequest}}}} requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayQualityWorkRequest}}}}
responses: responses:
'200': {description: Leased quality work or reconciliation observation., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayQualityWork}}}} '200': {description: 'Leased quality work or reconciliation observation. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayQualityWork}}}}
'204': {description: No matching work is available.} '204': {description: No matching work is available.}
'400': {$ref: '#/components/responses/InvalidRequest'} '400': {$ref: '#/components/responses/InvalidRequest'}
'401': {$ref: '#/components/responses/Unauthorized'} '401': {$ref: '#/components/responses/Unauthorized'}
@@ -435,8 +440,9 @@ paths:
post: post:
tags: [gateway] tags: [gateway]
operationId: acknowledgeGatewayQualityWork operationId: acknowledgeGatewayQualityWork
description: The authenticated gateway mTLS certificate identity MUST match `gateway_id`; Server authority MUST exactly match the session, gateway, reconnect sequence, operation, revision, and monotonic lease generation tuple. Stale lease generations MUST be rejected. Maximum JSON body: 16384 bytes.
security: [{gatewayMutualTLS: []}] security: [{gatewayMutualTLS: []}]
requestBody: {required: true, content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayQualityAck}}}} requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayQualityAck}}}}
responses: responses:
'204': {description: Quality acknowledgement committed idempotently.} '204': {description: Quality acknowledgement committed idempotently.}
'400': {$ref: '#/components/responses/InvalidRequest'} '400': {$ref: '#/components/responses/InvalidRequest'}
@@ -446,10 +452,11 @@ paths:
post: post:
tags: [gateway] tags: [gateway]
operationId: acquireGatewayStopWork operationId: acquireGatewayStopWork
description: The authenticated gateway mTLS certificate identity MUST match `gateway_id`; Server authority MUST match the session, gateway, reconnect sequence, and any supplied operation coordinate. A `poll` acquisition omits the unknown operation ID and discovers pending session work after a lost prompt; a `prompt` coordinate MUST match exactly. Maximum JSON body: 16384 bytes.
security: [{gatewayMutualTLS: []}] security: [{gatewayMutualTLS: []}]
requestBody: {required: true, content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayStopWorkRequest}}}} requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayStopWorkRequest}}}}
responses: responses:
'200': {description: One-way non-reissued Stop work claim., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayStopWork}}}} '200': {description: 'One-way non-reissued Stop work claim. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayStopWork}}}}
'204': {description: No matching Stop work is available.} '204': {description: No matching Stop work is available.}
'400': {$ref: '#/components/responses/InvalidRequest'} '400': {$ref: '#/components/responses/InvalidRequest'}
'401': {$ref: '#/components/responses/Unauthorized'} '401': {$ref: '#/components/responses/Unauthorized'}
@@ -458,8 +465,9 @@ paths:
post: post:
tags: [gateway] tags: [gateway]
operationId: acknowledgeGatewayStopWork operationId: acknowledgeGatewayStopWork
description: The authenticated gateway mTLS certificate identity MUST match `gateway_id`; Server authority MUST exactly match the session, gateway, reconnect sequence, and operation tuple. Maximum JSON body: 16384 bytes.
security: [{gatewayMutualTLS: []}] security: [{gatewayMutualTLS: []}]
requestBody: {required: true, content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayStopAck}}}} requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayStopAck}}}}
responses: responses:
'204': {description: Stop acknowledgement committed idempotently.} '204': {description: Stop acknowledgement committed idempotently.}
'400': {$ref: '#/components/responses/InvalidRequest'} '400': {$ref: '#/components/responses/InvalidRequest'}
@@ -556,7 +564,7 @@ components:
name: operation_id name: operation_id
in: path in: path
required: true required: true
schema: {type: string, format: uuid} schema: {type: string, format: uuid, pattern: '^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'}
Limit: Limit:
name: limit name: limit
in: query in: query
+7 -2
View File
@@ -205,8 +205,10 @@ message GatewayQualityWorkRequest {
string gateway_id = 3; string gateway_id = 3;
uint64 reconnect_sequence = 4; uint64 reconnect_sequence = 4;
string operation_id = 5; string operation_id = 5;
uint64 revision = 6; optional uint64 revision = 6;
optional uint64 current_applied_revision = 7; optional uint64 current_applied_revision = 7;
string acquisition = 8;
optional uint64 lease_generation = 9;
} }
message GatewayQualityWork { message GatewayQualityWork {
@@ -219,6 +221,7 @@ message GatewayQualityWork {
google.protobuf.Timestamp lease_expires_at = 7; google.protobuf.Timestamp lease_expires_at = 7;
SelectedSessionDescriptor selected_descriptor = 8; SelectedSessionDescriptor selected_descriptor = 8;
optional uint64 current_applied_revision = 9; optional uint64 current_applied_revision = 9;
uint64 lease_generation = 10;
} }
message GatewayQualityAck { message GatewayQualityAck {
@@ -229,8 +232,9 @@ message GatewayQualityAck {
string operation_id = 5; string operation_id = 5;
uint64 revision = 6; uint64 revision = 6;
string outcome = 7; string outcome = 7;
uint64 current_applied_revision = 8; optional uint64 current_applied_revision = 8;
string failure_code = 9; string failure_code = 9;
uint64 lease_generation = 10;
} }
message GatewayStopWork { message GatewayStopWork {
@@ -248,6 +252,7 @@ message GatewayStopWorkRequest {
string gateway_id = 3; string gateway_id = 3;
uint64 reconnect_sequence = 4; uint64 reconnect_sequence = 4;
string operation_id = 5; string operation_id = 5;
string acquisition = 6;
} }
message GatewayStopAck { message GatewayStopAck {
+37 -20
View File
@@ -269,6 +269,7 @@
}, },
"SessionQualityLimits": { "SessionQualityLimits": {
"type": "object", "type": "object",
"x-max-bytes": 16384,
"additionalProperties": false, "additionalProperties": false,
"required": ["policy_version_id", "policy_display_limit", "selectable_display_maximum", "display_limit_override", "bitrate_minimum_kbps", "bitrate_target_kbps", "bitrate_maximum_kbps"], "required": ["policy_version_id", "policy_display_limit", "selectable_display_maximum", "display_limit_override", "bitrate_minimum_kbps", "bitrate_target_kbps", "bitrate_maximum_kbps"],
"properties": { "properties": {
@@ -654,6 +655,7 @@
}, },
"DisplayLimitOverrideRequest": { "DisplayLimitOverrideRequest": {
"type": "object", "type": "object",
"x-max-bytes": 16384,
"additionalProperties": false, "additionalProperties": false,
"required": ["allow_display_limit_override", "expected_version"], "required": ["allow_display_limit_override", "expected_version"],
"properties": { "properties": {
@@ -663,6 +665,7 @@
}, },
"QualityChangeRequest": { "QualityChangeRequest": {
"type": "object", "type": "object",
"x-max-bytes": 16384,
"additionalProperties": false, "additionalProperties": false,
"required": ["bitrate_preference", "expected_session_version", "expected_policy_version"], "required": ["bitrate_preference", "expected_session_version", "expected_policy_version"],
"properties": { "properties": {
@@ -673,10 +676,11 @@
}, },
"QualityChangeOperation": { "QualityChangeOperation": {
"type": "object", "type": "object",
"x-max-bytes": 16384,
"additionalProperties": false, "additionalProperties": false,
"required": ["operation_id", "session_id", "revision", "state", "requested_bitrate_preference", "effective_bitrate_kbps", "governing_policy_version", "session_version", "created_at", "deadline_at", "updated_at"], "required": ["operation_id", "session_id", "revision", "state", "requested_bitrate_preference", "effective_bitrate_kbps", "governing_policy_version", "session_version", "created_at", "deadline_at", "updated_at"],
"properties": { "properties": {
"operation_id": {"type": "string", "format": "uuid", "minLength": 36, "maxLength": 36}, "operation_id": {"type": "string", "format": "uuid", "pattern": "^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36},
"session_id": {"type": "string", "minLength": 1, "maxLength": 128}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
"revision": {"type": "integer", "minimum": 1}, "revision": {"type": "integer", "minimum": 1},
"state": {"type": "string", "enum": ["pending", "applying", "applied", "failed", "timed_out"]}, "state": {"type": "string", "enum": ["pending", "applying", "applied", "failed", "timed_out"]},
@@ -684,84 +688,97 @@
"effective_bitrate_kbps": {"type": "integer", "minimum": 100, "maximum": 1000000}, "effective_bitrate_kbps": {"type": "integer", "minimum": 100, "maximum": 1000000},
"governing_policy_version": {"type": "string", "minLength": 1, "maxLength": 128}, "governing_policy_version": {"type": "string", "minLength": 1, "maxLength": 128},
"session_version": {"type": "integer", "minimum": 1}, "session_version": {"type": "integer", "minimum": 1},
"created_at": {"type": "string", "format": "date-time", "maxLength": 64}, "created_at": {"type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{0,8}[1-9])?Z$", "maxLength": 64},
"deadline_at": {"type": "string", "format": "date-time", "maxLength": 64}, "deadline_at": {"type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{0,8}[1-9])?Z$", "maxLength": 64},
"updated_at": {"type": "string", "format": "date-time", "maxLength": 64}, "updated_at": {"type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{0,8}[1-9])?Z$", "maxLength": 64},
"failure_code": {"type": "string", "minLength": 1, "maxLength": 128} "failure_code": {"type": "string", "minLength": 1, "maxLength": 128}
} }
}, },
"StopOperation": { "StopOperation": {
"type": "object", "type": "object",
"x-max-bytes": 16384,
"additionalProperties": false, "additionalProperties": false,
"required": ["operation_id", "session_id", "state", "session_version", "created_at", "deadline_at", "updated_at"], "required": ["operation_id", "session_id", "state", "session_version", "created_at", "deadline_at", "updated_at"],
"properties": { "properties": {
"operation_id": {"type": "string", "format": "uuid", "minLength": 36, "maxLength": 36}, "operation_id": {"type": "string", "format": "uuid", "pattern": "^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36},
"session_id": {"type": "string", "minLength": 1, "maxLength": 128}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
"state": {"type": "string", "enum": ["pending", "attempting", "applied", "failed", "termination_unconfirmed"]}, "state": {"type": "string", "enum": ["pending", "attempting", "applied", "failed", "termination_unconfirmed"]},
"session_version": {"type": "integer", "minimum": 1}, "session_version": {"type": "integer", "minimum": 1},
"created_at": {"type": "string", "format": "date-time", "maxLength": 64}, "created_at": {"type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{0,8}[1-9])?Z$", "maxLength": 64},
"deadline_at": {"type": "string", "format": "date-time", "maxLength": 64}, "deadline_at": {"type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{0,8}[1-9])?Z$", "maxLength": 64},
"updated_at": {"type": "string", "format": "date-time", "maxLength": 64}, "updated_at": {"type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{0,8}[1-9])?Z$", "maxLength": 64},
"failure_code": {"type": "string", "minLength": 1, "maxLength": 128} "failure_code": {"type": "string", "minLength": 1, "maxLength": 128}
} }
}, },
"GatewayQualityWorkRequest": { "GatewayQualityWorkRequest": {
"type": "object", "additionalProperties": false, "type": "object", "additionalProperties": false,
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "revision"], "x-max-bytes": 16384,
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "acquisition"],
"properties": { "properties": {
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128}, "version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0}, "gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
"operation_id": {"type": "string", "format": "uuid", "minLength": 36, "maxLength": 36}, "revision": {"type": "integer", "minimum": 1}, "acquisition": {"type": "string", "enum": ["poll", "prompt", "observation"]},
"operation_id": {"type": "string", "format": "uuid", "pattern": "^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36},
"revision": {"type": "integer", "minimum": 1, "x-optional-pointer": true},
"lease_generation": {"type": "integer", "minimum": 1, "x-optional-pointer": true},
"current_applied_revision": {"type": "integer", "minimum": 0, "x-optional-pointer": true} "current_applied_revision": {"type": "integer", "minimum": 0, "x-optional-pointer": true}
} }
}, },
"GatewayQualityWork": { "GatewayQualityWork": {
"type": "object", "additionalProperties": false, "type": "object", "additionalProperties": false,
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "revision", "lease_expires_at", "selected_descriptor"], "x-max-bytes": 16384,
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "revision", "lease_generation", "lease_expires_at", "selected_descriptor"],
"properties": { "properties": {
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128}, "version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0}, "gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
"operation_id": {"type": "string", "format": "uuid", "minLength": 36, "maxLength": 36}, "revision": {"type": "integer", "minimum": 1}, "operation_id": {"type": "string", "format": "uuid", "pattern": "^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36}, "revision": {"type": "integer", "minimum": 1},
"lease_expires_at": {"type": "string", "format": "date-time", "maxLength": 64}, "selected_descriptor": {"$ref": "#/$defs/SelectedSessionDescriptor"}, "lease_generation": {"type": "integer", "minimum": 1},
"lease_expires_at": {"type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{0,8}[1-9])?Z$", "maxLength": 64}, "selected_descriptor": {"$ref": "#/$defs/SelectedSessionDescriptor"},
"current_applied_revision": {"type": "integer", "minimum": 0, "x-optional-pointer": true} "current_applied_revision": {"type": "integer", "minimum": 0, "x-optional-pointer": true}
} }
}, },
"GatewayQualityAck": { "GatewayQualityAck": {
"type": "object", "additionalProperties": false, "type": "object", "additionalProperties": false,
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "revision", "outcome", "current_applied_revision"], "x-max-bytes": 16384,
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "revision", "lease_generation", "outcome"],
"properties": { "properties": {
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128}, "version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0}, "gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
"operation_id": {"type": "string", "format": "uuid", "minLength": 36, "maxLength": 36}, "revision": {"type": "integer", "minimum": 1}, "operation_id": {"type": "string", "format": "uuid", "pattern": "^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36}, "revision": {"type": "integer", "minimum": 1},
"outcome": {"type": "string", "enum": ["applied", "not_applied", "uncertain"]}, "current_applied_revision": {"type": "integer", "minimum": 0}, "lease_generation": {"type": "integer", "minimum": 1},
"outcome": {"type": "string", "enum": ["applied", "proven_prior", "unknown"]}, "current_applied_revision": {"type": "integer", "minimum": 0, "x-optional-pointer": true},
"failure_code": {"type": "string", "minLength": 1, "maxLength": 128} "failure_code": {"type": "string", "minLength": 1, "maxLength": 128}
} }
}, },
"GatewayStopWorkRequest": { "GatewayStopWorkRequest": {
"type": "object", "additionalProperties": false, "type": "object", "additionalProperties": false,
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id"], "x-max-bytes": 16384,
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "acquisition"],
"properties": { "properties": {
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128}, "version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0}, "gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
"operation_id": {"type": "string", "format": "uuid", "minLength": 36, "maxLength": 36} "acquisition": {"type": "string", "enum": ["poll", "prompt"]},
"operation_id": {"type": "string", "format": "uuid", "pattern": "^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36}
} }
}, },
"GatewayStopWork": { "GatewayStopWork": {
"type": "object", "additionalProperties": false, "type": "object", "additionalProperties": false,
"x-max-bytes": 16384,
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "attempt"], "required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "attempt"],
"properties": { "properties": {
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128}, "version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0}, "gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
"operation_id": {"type": "string", "format": "uuid", "minLength": 36, "maxLength": 36}, "attempt": {"type": "integer", "minimum": 1, "maximum": 1} "operation_id": {"type": "string", "format": "uuid", "pattern": "^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36}, "attempt": {"type": "integer", "minimum": 1, "maximum": 1}
} }
}, },
"GatewayStopAck": { "GatewayStopAck": {
"type": "object", "additionalProperties": false, "type": "object", "additionalProperties": false,
"x-max-bytes": 16384,
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "outcome"], "required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "outcome"],
"properties": { "properties": {
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128}, "version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0}, "gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
"operation_id": {"type": "string", "format": "uuid", "minLength": 36, "maxLength": 36}, "outcome": {"type": "string", "enum": ["applied", "failed", "termination_unconfirmed"]}, "operation_id": {"type": "string", "format": "uuid", "pattern": "^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36}, "outcome": {"type": "string", "enum": ["applied", "failed", "termination_unconfirmed"]},
"failure_code": {"type": "string", "minLength": 1, "maxLength": 128} "failure_code": {"type": "string", "minLength": 1, "maxLength": 128}
} }
}, },
+120
View File
@@ -89,11 +89,125 @@ func TestRC6BitratePreferenceTaggedBounds(t *testing.T) {
} }
} }
func TestRC6GatewayQualityDiscoveryAndLeaseFencing(t *testing.T) {
operationID := "12345678-1234-1234-1234-123456789abc"
revision := int64(7)
leaseGeneration := int64(3)
currentRevision := int64(6)
poll := protocol.GatewayQualityWorkRequest{Version: "1", SessionID: "session-1", GatewayID: "gateway-1", ReconnectSequence: 2, Acquisition: "poll"}
if err := poll.Validate(); err != nil {
t.Fatalf("session-bound lost-prompt poll rejected: %v", err)
}
prompt := poll
prompt.Acquisition = "prompt"
prompt.OperationID = operationID
prompt.Revision = &revision
if err := prompt.Validate(); err != nil {
t.Fatalf("prompt-bound acquisition rejected: %v", err)
}
observation := prompt
observation.Acquisition = "observation"
observation.LeaseGeneration = &leaseGeneration
observation.CurrentAppliedRevision = &currentRevision
if err := observation.Validate(); err != nil {
t.Fatalf("lease-bound applied-revision observation rejected: %v", err)
}
for name, invalid := range map[string]protocol.GatewayQualityWorkRequest{
"poll with unknown coordinates": prompt,
"prompt missing revision": {Version: "1", SessionID: "session-1", GatewayID: "gateway-1", Acquisition: "prompt", OperationID: operationID},
"observation missing lease": {Version: "1", SessionID: "session-1", GatewayID: "gateway-1", Acquisition: "observation", OperationID: operationID, Revision: &revision, CurrentAppliedRevision: &currentRevision},
} {
if name == "poll with unknown coordinates" {
invalid.Acquisition = "poll"
}
if invalid.Validate() == nil {
t.Fatalf("accepted invalid %s: %+v", name, invalid)
}
}
stopPoll := protocol.GatewayStopWorkRequest{Version: "1", SessionID: "session-1", GatewayID: "gateway-1", ReconnectSequence: 2, Acquisition: "poll"}
if err := stopPoll.Validate(); err != nil {
t.Fatalf("session-bound Stop poll rejected: %v", err)
}
stopPrompt := stopPoll
stopPrompt.Acquisition = "prompt"
stopPrompt.OperationID = operationID
if err := stopPrompt.Validate(); err != nil {
t.Fatalf("prompt-bound Stop acquisition rejected: %v", err)
}
stopPoll.OperationID = operationID
if stopPoll.Validate() == nil {
t.Fatal("Stop poll accepted unknown operation coordinates")
}
}
func TestRC6GatewayQualityAckHasUnambiguousObservation(t *testing.T) {
revision := int64(7)
prior := int64(6)
base := protocol.GatewayQualityAck{
Version: "1", SessionID: "session-1", GatewayID: "gateway-1", ReconnectSequence: 2,
OperationID: "12345678-1234-1234-1234-123456789abc", Revision: revision, LeaseGeneration: 3,
}
for _, valid := range []protocol.GatewayQualityAck{
func() protocol.GatewayQualityAck {
value := base
value.Outcome = "applied"
value.CurrentAppliedRevision = &revision
return value
}(),
func() protocol.GatewayQualityAck {
value := base
value.Outcome = "proven_prior"
value.CurrentAppliedRevision = &prior
return value
}(),
func() protocol.GatewayQualityAck { value := base; value.Outcome = "unknown"; return value }(),
} {
if err := valid.Validate(); err != nil {
t.Fatalf("valid quality acknowledgement rejected: %+v: %v", valid, err)
}
}
for _, invalid := range []protocol.GatewayQualityAck{
func() protocol.GatewayQualityAck {
value := base
value.Outcome = "applied"
value.CurrentAppliedRevision = &prior
return value
}(),
func() protocol.GatewayQualityAck {
value := base
value.Outcome = "proven_prior"
value.CurrentAppliedRevision = &revision
return value
}(),
func() protocol.GatewayQualityAck {
value := base
value.Outcome = "unknown"
value.CurrentAppliedRevision = &prior
return value
}(),
func() protocol.GatewayQualityAck {
value := base
value.Outcome = "unknown"
value.LeaseGeneration = 0
return value
}(),
} {
if invalid.Validate() == nil {
t.Fatalf("contradictory or unfenced quality acknowledgement accepted: %+v", invalid)
}
}
}
func TestRC6JSONFixturesAndCanonicalOperationFields(t *testing.T) { func TestRC6JSONFixturesAndCanonicalOperationFields(t *testing.T) {
validFixtures := map[string]func([]byte) error{ validFixtures := map[string]func([]byte) error{
"../../fixtures/valid/session-request.json": func(raw []byte) error { _, err := protocol.DecodeSessionRequest(raw); return err }, "../../fixtures/valid/session-request.json": func(raw []byte) error { _, err := protocol.DecodeSessionRequest(raw); return err },
"../../fixtures/valid/selected-session-descriptor.json": func(raw []byte) error { _, err := protocol.DecodeSelectedSessionDescriptor(raw); return err }, "../../fixtures/valid/selected-session-descriptor.json": func(raw []byte) error { _, err := protocol.DecodeSelectedSessionDescriptor(raw); return err },
"../../fixtures/valid/session-quality-limits.json": func(raw []byte) error { _, err := protocol.DecodeSessionQualityLimits(raw); return err }, "../../fixtures/valid/session-quality-limits.json": func(raw []byte) error { _, err := protocol.DecodeSessionQualityLimits(raw); return err },
"../../fixtures/valid/gateway-quality-poll.json": func(raw []byte) error { _, err := protocol.DecodeGatewayQualityWorkRequest(raw); return err },
"../../fixtures/valid/gateway-quality-ack-applied.json": func(raw []byte) error { _, err := protocol.DecodeGatewayQualityAck(raw); return err },
} }
for path, decode := range validFixtures { for path, decode := range validFixtures {
raw, err := os.ReadFile(path) raw, err := os.ReadFile(path)
@@ -111,6 +225,12 @@ func TestRC6JSONFixturesAndCanonicalOperationFields(t *testing.T) {
"../../fixtures/invalid/video-profile-unknown.json": func(raw []byte) error { _, err := protocol.DecodeVideoProfile(raw); return err }, "../../fixtures/invalid/video-profile-unknown.json": func(raw []byte) error { _, err := protocol.DecodeVideoProfile(raw); return err },
"../../fixtures/invalid/bitrate-preference-auto-target.json": func(raw []byte) error { _, err := protocol.DecodeBitratePreference(raw); return err }, "../../fixtures/invalid/bitrate-preference-auto-target.json": func(raw []byte) error { _, err := protocol.DecodeBitratePreference(raw); return err },
"../../fixtures/invalid/selected-session-descriptor-provider-field.json": func(raw []byte) error { _, err := protocol.DecodeSelectedSessionDescriptor(raw); return err }, "../../fixtures/invalid/selected-session-descriptor-provider-field.json": func(raw []byte) error { _, err := protocol.DecodeSelectedSessionDescriptor(raw); return err },
"../../fixtures/invalid/gateway-quality-poll-coordinates.json": func(raw []byte) error { _, err := protocol.DecodeGatewayQualityWorkRequest(raw); return err },
"../../fixtures/invalid/gateway-quality-ack-contradictory.json": func(raw []byte) error { _, err := protocol.DecodeGatewayQualityAck(raw); return err },
"../../fixtures/invalid/gateway-quality-ack-uppercase-uuid.json": func(raw []byte) error { _, err := protocol.DecodeGatewayQualityAck(raw); return err },
"../../fixtures/invalid/gateway-quality-ack-zero-uuid.json": func(raw []byte) error { _, err := protocol.DecodeGatewayQualityAck(raw); return err },
"../../fixtures/invalid/quality-operation-offset-time.json": func(raw []byte) error { _, err := protocol.DecodeQualityChangeOperation(raw); return err },
"../../fixtures/invalid/quality-operation-noncanonical-fraction.json": func(raw []byte) error { _, err := protocol.DecodeQualityChangeOperation(raw); return err },
} }
for path, decode := range invalidFixtures { for path, decode := range invalidFixtures {
raw, err := os.ReadFile(path) raw, err := os.ReadFile(path)
+36 -4
View File
@@ -183,6 +183,16 @@ def go_validation(definition: dict[str, Any]) -> list[str]:
lines.append("\tif v.BitrateTargetKbps > v.BitrateMaximumKbps { violations = append(violations, FieldViolation{Field: \"bitrate_bounds\", Code: \"invalid_order\"}) }") lines.append("\tif v.BitrateTargetKbps > v.BitrateMaximumKbps { violations = append(violations, FieldViolation{Field: \"bitrate_bounds\", Code: \"invalid_order\"}) }")
if name == "BitratePreference": if name == "BitratePreference":
lines.append("\tif v.Mode == \"auto\" && v.TargetKbps != nil || v.Mode == \"explicit\" && v.TargetKbps == nil { violations = append(violations, FieldViolation{Field: \"target_kbps\", Code: \"invalid_tagged_value\"}) }") lines.append("\tif v.Mode == \"auto\" && v.TargetKbps != nil || v.Mode == \"explicit\" && v.TargetKbps == nil { violations = append(violations, FieldViolation{Field: \"target_kbps\", Code: \"invalid_tagged_value\"}) }")
if name == "GatewayQualityWorkRequest":
lines.append("\tif v.Acquisition == \"poll\" && (v.OperationID != \"\" || v.Revision != nil || v.LeaseGeneration != nil || v.CurrentAppliedRevision != nil) { violations = append(violations, FieldViolation{Field: \"acquisition\", Code: \"invalid_tagged_value\"}) }")
lines.append("\tif v.Acquisition == \"prompt\" && (v.OperationID == \"\" || v.Revision == nil || v.LeaseGeneration != nil || v.CurrentAppliedRevision != nil) { violations = append(violations, FieldViolation{Field: \"acquisition\", Code: \"invalid_tagged_value\"}) }")
lines.append("\tif v.Acquisition == \"observation\" && (v.OperationID == \"\" || v.Revision == nil || v.LeaseGeneration == nil || v.CurrentAppliedRevision == nil) { violations = append(violations, FieldViolation{Field: \"acquisition\", Code: \"invalid_tagged_value\"}) }")
if name == "GatewayStopWorkRequest":
lines.append("\tif v.Acquisition == \"poll\" && v.OperationID != \"\" || v.Acquisition == \"prompt\" && v.OperationID == \"\" { violations = append(violations, FieldViolation{Field: \"acquisition\", Code: \"invalid_tagged_value\"}) }")
if name == "GatewayQualityAck":
lines.append("\tif v.Outcome == \"applied\" && (v.CurrentAppliedRevision == nil || *v.CurrentAppliedRevision != v.Revision) { violations = append(violations, FieldViolation{Field: \"current_applied_revision\", Code: \"invalid_tagged_value\"}) }")
lines.append("\tif v.Outcome == \"proven_prior\" && (v.CurrentAppliedRevision == nil || *v.CurrentAppliedRevision >= v.Revision) { violations = append(violations, FieldViolation{Field: \"current_applied_revision\", Code: \"invalid_tagged_value\"}) }")
lines.append("\tif v.Outcome == \"unknown\" && v.CurrentAppliedRevision != nil { violations = append(violations, FieldViolation{Field: \"current_applied_revision\", Code: \"invalid_tagged_value\"}) }")
if name == "GatewayRegistration": if name == "GatewayRegistration":
lines.append("\tif v.ProtocolMinVersion > v.ProtocolMaxVersion { violations = append(violations, FieldViolation{Field: \"protocol_version\", Code: \"invalid_order\"}) }") lines.append("\tif v.ProtocolMinVersion > v.ProtocolMaxVersion { violations = append(violations, FieldViolation{Field: \"protocol_version\", Code: \"invalid_order\"}) }")
if name == "ChannelFrame": if name == "ChannelFrame":
@@ -471,6 +481,16 @@ def rust_validation(definition: dict[str, Any]) -> list[str]:
lines.append(" if self.bitrateTargetKbps > self.bitrateMaximumKbps { return Err(ValidationError::new(\"bitrate_bounds\", \"invalid_order\")); }") lines.append(" if self.bitrateTargetKbps > self.bitrateMaximumKbps { return Err(ValidationError::new(\"bitrate_bounds\", \"invalid_order\")); }")
if name == "BitratePreference": if name == "BitratePreference":
lines.append(" if self.mode == \"auto\" && self.targetKbps.is_some() || self.mode == \"explicit\" && self.targetKbps.is_none() { return Err(ValidationError::new(\"target_kbps\", \"invalid_tagged_value\")); }") lines.append(" if self.mode == \"auto\" && self.targetKbps.is_some() || self.mode == \"explicit\" && self.targetKbps.is_none() { return Err(ValidationError::new(\"target_kbps\", \"invalid_tagged_value\")); }")
if name == "GatewayQualityWorkRequest":
lines.append(" if self.acquisition == \"poll\" && (self.operationId.is_some() || self.revision.is_some() || self.leaseGeneration.is_some() || self.currentAppliedRevision.is_some()) { return Err(ValidationError::new(\"acquisition\", \"invalid_tagged_value\")); }")
lines.append(" if self.acquisition == \"prompt\" && (self.operationId.is_none() || self.revision.is_none() || self.leaseGeneration.is_some() || self.currentAppliedRevision.is_some()) { return Err(ValidationError::new(\"acquisition\", \"invalid_tagged_value\")); }")
lines.append(" if self.acquisition == \"observation\" && (self.operationId.is_none() || self.revision.is_none() || self.leaseGeneration.is_none() || self.currentAppliedRevision.is_none()) { return Err(ValidationError::new(\"acquisition\", \"invalid_tagged_value\")); }")
if name == "GatewayStopWorkRequest":
lines.append(" if self.acquisition == \"poll\" && self.operationId.is_some() || self.acquisition == \"prompt\" && self.operationId.is_none() { return Err(ValidationError::new(\"acquisition\", \"invalid_tagged_value\")); }")
if name == "GatewayQualityAck":
lines.append(" if self.outcome == \"applied\" && self.currentAppliedRevision != Some(self.revision) { return Err(ValidationError::new(\"current_applied_revision\", \"invalid_tagged_value\")); }")
lines.append(" if self.outcome == \"proven_prior\" && self.currentAppliedRevision.map_or(true, |current| current >= self.revision) { return Err(ValidationError::new(\"current_applied_revision\", \"invalid_tagged_value\")); }")
lines.append(" if self.outcome == \"unknown\" && self.currentAppliedRevision.is_some() { return Err(ValidationError::new(\"current_applied_revision\", \"invalid_tagged_value\")); }")
if name == "GatewayRegistration": if name == "GatewayRegistration":
lines.append(" if self.protocolMinVersion > self.protocolMaxVersion { return Err(ValidationError::new(\"protocol_version\", \"invalid_order\")); }") lines.append(" if self.protocolMinVersion > self.protocolMaxVersion { return Err(ValidationError::new(\"protocol_version\", \"invalid_order\")); }")
if name == "ChannelFrame": if name == "ChannelFrame":
@@ -687,6 +707,16 @@ def swift_validation(definition: dict[str, Any]) -> list[str]:
lines.append(" if bitrateTargetKbps > bitrateMaximumKbps { throw ContractValidationError(field: \"bitrate_bounds\", code: \"invalid_order\") }") lines.append(" if bitrateTargetKbps > bitrateMaximumKbps { throw ContractValidationError(field: \"bitrate_bounds\", code: \"invalid_order\") }")
if name == "BitratePreference": if name == "BitratePreference":
lines.append(" if mode == \"auto\" && targetKbps != nil || mode == \"explicit\" && targetKbps == nil { throw ContractValidationError(field: \"target_kbps\", code: \"invalid_tagged_value\") }") lines.append(" if mode == \"auto\" && targetKbps != nil || mode == \"explicit\" && targetKbps == nil { throw ContractValidationError(field: \"target_kbps\", code: \"invalid_tagged_value\") }")
if name == "GatewayQualityWorkRequest":
lines.append(" if acquisition == \"poll\" && (operationId != nil || revision != nil || leaseGeneration != nil || currentAppliedRevision != nil) { throw ContractValidationError(field: \"acquisition\", code: \"invalid_tagged_value\") }")
lines.append(" if acquisition == \"prompt\" && (operationId == nil || revision == nil || leaseGeneration != nil || currentAppliedRevision != nil) { throw ContractValidationError(field: \"acquisition\", code: \"invalid_tagged_value\") }")
lines.append(" if acquisition == \"observation\" && (operationId == nil || revision == nil || leaseGeneration == nil || currentAppliedRevision == nil) { throw ContractValidationError(field: \"acquisition\", code: \"invalid_tagged_value\") }")
if name == "GatewayStopWorkRequest":
lines.append(" if acquisition == \"poll\" && operationId != nil || acquisition == \"prompt\" && operationId == nil { throw ContractValidationError(field: \"acquisition\", code: \"invalid_tagged_value\") }")
if name == "GatewayQualityAck":
lines.append(" if outcome == \"applied\" && currentAppliedRevision != revision { throw ContractValidationError(field: \"current_applied_revision\", code: \"invalid_tagged_value\") }")
lines.append(" if outcome == \"proven_prior\" && (currentAppliedRevision == nil || currentAppliedRevision! >= revision) { throw ContractValidationError(field: \"current_applied_revision\", code: \"invalid_tagged_value\") }")
lines.append(" if outcome == \"unknown\" && currentAppliedRevision != nil { throw ContractValidationError(field: \"current_applied_revision\", code: \"invalid_tagged_value\") }")
if name == "GatewayRegistration": if name == "GatewayRegistration":
lines.append(" if protocolMinVersion > protocolMaxVersion { throw ContractValidationError(field: \"protocol_version\", code: \"invalid_order\") }") lines.append(" if protocolMinVersion > protocolMaxVersion { throw ContractValidationError(field: \"protocol_version\", code: \"invalid_order\") }")
if name == "ChannelFrame": if name == "ChannelFrame":
@@ -706,6 +736,7 @@ def generate_swift(defs: dict[str, dict[str, Any]], schema_hash: str, compatibil
"public struct ContractValidationError: Error, Equatable { public let field: String; public let code: String }", "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 } }", "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 } }",
"private func rejectDuplicateJSONKeys(_ data: Data) throws {", "private func rejectDuplicateJSONKeys(_ data: Data) throws {",
" guard data.count <= 1_048_576 else { throw ContractValidationError(field: \"json\", code: \"payload_too_large\") }",
" var index = 0", " var index = 0",
" func skipWhitespace() { while index < data.count && [9, 10, 13, 32].contains(data[index]) { index += 1 } }", " func skipWhitespace() { while index < data.count && [9, 10, 13, 32].contains(data[index]) { index += 1 } }",
" func parseString() throws -> String {", " func parseString() throws -> String {",
@@ -719,7 +750,8 @@ def generate_swift(defs: dict[str, dict[str, Any]], schema_hash: str, compatibil
" }", " }",
" throw ContractValidationError(field: \"json\", code: \"invalid_json\")", " throw ContractValidationError(field: \"json\", code: \"invalid_json\")",
" }", " }",
" func parseValue() throws {", " func parseValue(_ depth: Int) throws {",
" guard depth <= 64 else { throw ContractValidationError(field: \"json\", code: \"nesting_too_deep\") }",
" skipWhitespace()", " skipWhitespace()",
" guard index < data.count else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }", " guard index < data.count else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
" if data[index] == 123 {", " if data[index] == 123 {",
@@ -734,7 +766,7 @@ def generate_swift(defs: dict[str, dict[str, Any]], schema_hash: str, compatibil
" skipWhitespace()", " skipWhitespace()",
" guard index < data.count, data[index] == 58 else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }", " guard index < data.count, data[index] == 58 else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
" index += 1", " index += 1",
" try parseValue()", " try parseValue(depth + 1)",
" skipWhitespace()", " skipWhitespace()",
" guard index < data.count else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }", " guard index < data.count else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
" if data[index] == 125 { index += 1; return }", " if data[index] == 125 { index += 1; return }",
@@ -747,7 +779,7 @@ def generate_swift(defs: dict[str, dict[str, Any]], schema_hash: str, compatibil
" skipWhitespace()", " skipWhitespace()",
" if index < data.count, data[index] == 93 { index += 1; return }", " if index < data.count, data[index] == 93 { index += 1; return }",
" while true {", " while true {",
" try parseValue()", " try parseValue(depth + 1)",
" skipWhitespace()", " skipWhitespace()",
" guard index < data.count else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }", " guard index < data.count else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
" if data[index] == 93 { index += 1; return }", " if data[index] == 93 { index += 1; return }",
@@ -760,7 +792,7 @@ def generate_swift(defs: dict[str, dict[str, Any]], schema_hash: str, compatibil
" while index < data.count && ![9, 10, 13, 32, 44, 93, 125].contains(data[index]) { index += 1 }", " while index < data.count && ![9, 10, 13, 32, 44, 93, 125].contains(data[index]) { index += 1 }",
" guard index > start else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }", " guard index > start else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
" }", " }",
" try parseValue()", " try parseValue(0)",
" skipWhitespace()", " skipWhitespace()",
" guard index == data.count else { throw ContractValidationError(field: \"json\", code: \"trailing_json\") }", " guard index == data.count else { throw ContractValidationError(field: \"json\", code: \"trailing_json\") }",
"}", "}",
+87 -4
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 display = try DisplayMode(resolutionWidth: 2560, resolutionHeight: 1440, fps: 120)
let adjustment = try SessionAdjustment(displayReason: "none", bitrateReason: "none") 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 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( let capability = try CapabilityProfile(
transport: "quic-tls13", framing: "datagram-v1", media: "encoded", transport: "quic-tls13", framing: "datagram-v1", media: "encoded",
sourceRateControl: "server", videoProfiles: [video], audioProfiles: [audio] sourceRateControl: "server", videoProfiles: [video], audioProfiles: [audio]
@@ -191,6 +206,19 @@ do {
_ = try CapabilityProfile.decodeJSON(duplicateCapability) _ = try CapabilityProfile.decodeJSON(duplicateCapability)
fatalError("capability accepted duplicate JSON keys") fatalError("capability accepted duplicate JSON keys")
} catch { } } 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"] { for field in ["version", "session_id", "gateway_id", "audience", "reconnect_sequence", "expires_at", "capabilities", "selected_descriptor"] {
var missing = clientAuthorityObject var missing = clientAuthorityObject
missing.removeValue(forKey: field) missing.removeValue(forKey: field)
@@ -385,6 +413,15 @@ fn main() {
let display = DisplayMode::new(2560, 1440, 120).unwrap(); let display = DisplayMode::new(2560, 1440, 120).unwrap();
let adjustment = SessionAdjustment::new("none".into(), "none".into()).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 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( let capabilities = CapabilityProfile::new(
"quic-tls13".into(), "datagram-v1".into(), "encoded".into(), "quic-tls13".into(), "datagram-v1".into(), "encoded".into(),
"server".into(), vec![video.clone()], vec![audio.clone()], "server".into(), vec![video.clone()], vec![audio.clone()],
@@ -544,7 +581,8 @@ fn main() {
) )
run(["rustc", str(rust), "-o", str(workspace / "rust-contracts")], ROOT) run(["rustc", str(rust), "-o", str(workspace / "rust-contracts")], ROOT)
run([str(workspace / "rust-contracts")], ROOT) run([str(workspace / "rust-contracts")], ROOT)
expected_protobuf_fields = [ expected_protobuf_messages = {
"ClientSessionAuthority": [
("version", 1), ("version", 1),
("session_id", 2), ("session_id", 2),
("gateway_id", 3), ("gateway_id", 3),
@@ -553,11 +591,56 @@ fn main() {
("expires_at", 6), ("expires_at", 6),
("capabilities", 7), ("capabilities", 7),
("selected_descriptor", 8), ("selected_descriptor", 8),
] ],
actual_protobuf_fields = protobuf_message_fields("ClientSessionAuthority") "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: if actual_protobuf_fields != expected_protobuf_fields:
raise RuntimeError( raise RuntimeError(
f"ClientSessionAuthority protobuf fields = {actual_protobuf_fields}; " f"{message} protobuf fields = {actual_protobuf_fields}; "
f"want {expected_protobuf_fields}" f"want {expected_protobuf_fields}"
) )
rust_unknown = workspace / "unknown.rs" rust_unknown = workspace / "unknown.rs"
+34
View File
@@ -53,6 +53,27 @@ def main() -> int:
for owner in ("SessionAuthority", "ClientSessionAuthority"): for owner in ("SessionAuthority", "ClientSessionAuthority"):
assert defs[owner]["required"][-1] == "selected_descriptor" assert defs[owner]["required"][-1] == "selected_descriptor"
assert defs["ConnectionManifest"]["required"][-1] == "selected_descriptor" assert defs["ConnectionManifest"]["required"][-1] == "selected_descriptor"
quality_request = defs["GatewayQualityWorkRequest"]
assert quality_request["required"] == ["version", "session_id", "gateway_id", "reconnect_sequence", "acquisition"]
assert quality_request["properties"]["acquisition"]["enum"] == ["poll", "prompt", "observation"]
assert quality_request["properties"]["lease_generation"]["minimum"] == 1
assert defs["GatewayStopWorkRequest"]["required"] == ["version", "session_id", "gateway_id", "reconnect_sequence", "acquisition"]
assert defs["GatewayQualityWork"]["required"][-3:] == ["lease_generation", "lease_expires_at", "selected_descriptor"]
assert defs["GatewayQualityAck"]["required"][-3:] == ["revision", "lease_generation", "outcome"]
assert defs["GatewayQualityAck"]["properties"]["outcome"]["enum"] == ["applied", "proven_prior", "unknown"]
operation_id_pattern = r"^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
canonical_time_pattern = r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{0,8}[1-9])?Z$"
for definition in ("QualityChangeOperation", "StopOperation", "GatewayQualityWorkRequest", "GatewayQualityWork", "GatewayQualityAck", "GatewayStopWorkRequest", "GatewayStopWork", "GatewayStopAck"):
assert defs[definition]["properties"]["operation_id"]["pattern"] == operation_id_pattern, definition
assert defs[definition]["x-max-bytes"] == 16384, definition
for definition, fields in {
"QualityChangeOperation": ("created_at", "deadline_at", "updated_at"),
"StopOperation": ("created_at", "deadline_at", "updated_at"),
"GatewayQualityWork": ("lease_expires_at",),
}.items():
for field in fields:
assert defs[definition]["properties"][field]["pattern"] == canonical_time_pattern, (definition, field)
display_mode = defs["DisplayMode"] display_mode = defs["DisplayMode"]
assert display_mode["required"] == ["resolution_width", "resolution_height", "fps"] assert display_mode["required"] == ["resolution_width", "resolution_height", "fps"]
@@ -207,6 +228,18 @@ def main() -> int:
): ):
operation = openapi.split(f" operationId: {operation_id}\n", 1)[1].split(" responses:\n", 1)[0] operation = openapi.split(f" operationId: {operation_id}\n", 1)[1].split(" responses:\n", 1)[0]
assert "gatewayMutualTLS: []" in operation and "nativeBearer" not in operation and "browserSession" not in operation, operation_id assert "gatewayMutualTLS: []" in operation and "nativeBearer" not in operation and "browserSession" not in operation, operation_id
assert "certificate identity MUST match" in operation, operation_id
assert "Maximum JSON body: 16384 bytes." in operation, operation_id
quality_acquisition = openapi.split(" operationId: acquireGatewayQualityWork\n", 1)[1].split(" responses:\n", 1)[0]
assert "`poll` acquisition omits unknown operation coordinates" in quality_acquisition
assert "coordinates MUST match exactly" in quality_acquisition
quality_acknowledgement = openapi.split(" operationId: acknowledgeGatewayQualityWork\n", 1)[1].split(" responses:\n", 1)[0]
assert "Stale lease generations MUST be rejected" in quality_acknowledgement
for operation_id in ("createSessionQualityChange", "getSessionQualityChange", "createSessionStopOperation", "getSessionStopOperation"):
operation = openapi.split(f" operationId: {operation_id}\n", 1)[1].split(" responses:\n", 1)[0]
assert "owning principal and active device/key" in operation, operation_id
assert openapi.count("Maximum JSON body: 16384 bytes.") >= 8
assert operation_id_pattern in openapi
assert "provider_url" not in openapi and "vm_address" not 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] session_endpoint = openapi.split(" /api/v1/auth/session:", 1)[1].split("\n /api/", 1)[0]
assert "$defs/BrowserAuthenticatedSession" in session_endpoint assert "$defs/BrowserAuthenticatedSession" in session_endpoint
@@ -238,6 +271,7 @@ def main() -> int:
""" """
admin_override = openapi.split(" operationId: updateEntitlementDisplayLimitOverride\n", 1)[1].split(" responses:\n", 1)[0] admin_override = openapi.split(" operationId: updateEntitlementDisplayLimitOverride\n", 1)[1].split(" responses:\n", 1)[0]
assert browser_requirement.removeprefix(" ") in admin_override assert browser_requirement.removeprefix(" ") in admin_override
assert "Maximum JSON body: 16384 bytes." in admin_override
for operation_id in ( for operation_id in (
"issueReauthenticationGrant", "logoutSession", "registerDevice", "proveDevice", "revokeDevice", "issueReauthenticationGrant", "logoutSession", "registerDevice", "proveDevice", "revokeDevice",
"requestBrokerSession", "allocateBrokerSession", "reconnectBrokerSession", "cancelBrokerSession", "requestBrokerSession", "allocateBrokerSession", "reconnectBrokerSession", "cancelBrokerSession",