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

This commit is contained in:
sechmachine
2026-08-11 18:38:19 +07:00
parent 79d9e49497
commit afcd5d99db
32 changed files with 1565 additions and 149 deletions
+474 -19
View File
@@ -13,11 +13,11 @@ import (
"time"
)
const SchemaSHA256 = "b2bb0a8ac8ef56dbc0e1443eeb5b3028be9e71ec2f5fd8e73928d71b7cd9340c"
const SchemaSHA256 = "dea3dd210c53d5a2d37050dd6afd8b0ac5bb8edcb7ab25a02e4026489ce8a00f"
const ProtocolVersion = "1.0.0"
const CurrentWireVersion = "1"
const NMinus1WireVersion = "0"
const NMinus2WireVersion = "-1"
const CurrentWireVersion = "2"
const NMinus1WireVersion = "1"
const NMinus2WireVersion = "0"
type FieldViolation struct {
Field string `json:"field"`
@@ -69,6 +69,13 @@ type BrokerSession struct {
EffectiveDisplayMode *DisplayMode `json:"effective_display_mode,omitempty"`
}
type BrowserAuthenticatedSession struct {
Username string `json:"username"`
Provider string `json:"provider"`
Roles []string `json:"roles"`
Role string `json:"role"`
}
type CapabilityProfile struct {
Transport string `json:"transport"`
Framing string `json:"framing"`
@@ -281,6 +288,14 @@ type ManifestTunnel struct {
Features []string `json:"features"`
}
type NativeAuthenticatedSession struct {
Username string `json:"username"`
Provider string `json:"provider"`
Roles []string `json:"roles"`
Role string `json:"role"`
NativeIdentity NativeSessionIdentity `json:"native_identity"`
}
type NativeCredential struct {
DeviceID string `json:"device_id,omitempty"`
FamilyID string `json:"family_id"`
@@ -290,6 +305,19 @@ type NativeCredential struct {
RefreshExpiresAt string `json:"refresh_expires_at,omitempty"`
}
type NativeSessionIdentity struct {
ClientDeviceID string `json:"client_device_id"`
DeviceKeyID string `json:"device_key_id"`
}
type NativeTunnelCredential struct {
ClientDeviceID string `json:"client_device_id"`
DeviceKeyID string `json:"device_key_id"`
CertificateChainPem string `json:"certificate_chain_pem"`
TrustBundlePem string `json:"trust_bundle_pem"`
ExpiresAt string `json:"expires_at"`
}
type PageInfo struct {
Limit int64 `json:"limit"`
NextCursor string `json:"next_cursor"`
@@ -392,12 +420,11 @@ type SessionAuthority struct {
}
type SessionRequest struct {
ClientDeviceID string `json:"client_device_id"`
DeviceKeyID string `json:"device_key_id"`
PoolID string `json:"pool_id"`
IdempotencyKey string `json:"idempotency_key"`
PolicySnapshot AllocationPolicy `json:"policy_snapshot"`
RequestedDisplayMode *DisplayMode `json:"requested_display_mode,omitempty"`
ClientDeviceID string `json:"client_device_id"`
DeviceKeyID string `json:"device_key_id"`
PoolID string `json:"pool_id"`
IdempotencyKey string `json:"idempotency_key"`
RequestedDisplayMode *DisplayMode `json:"requested_display_mode,omitempty"`
}
type StableError struct {
@@ -863,6 +890,105 @@ func EncodeBrokerSession(value BrokerSession) ([]byte, error) {
return json.Marshal(value)
}
func (v BrowserAuthenticatedSession) Validate() error {
var violations []FieldViolation
if v.Username == "" {
violations = append(violations, FieldViolation{Field: "username", Code: "required"})
}
if len(v.Username) < 1 && v.Username != "" {
violations = append(violations, FieldViolation{Field: "username", Code: "min_length"})
}
if len(v.Username) > 256 {
violations = append(violations, FieldViolation{Field: "username", Code: "max_length"})
}
if v.Provider == "" {
violations = append(violations, FieldViolation{Field: "provider", Code: "required"})
}
if len(v.Provider) < 1 && v.Provider != "" {
violations = append(violations, FieldViolation{Field: "provider", Code: "min_length"})
}
if len(v.Provider) > 64 {
violations = append(violations, FieldViolation{Field: "provider", Code: "max_length"})
}
if v.Roles == nil {
violations = append(violations, FieldViolation{Field: "roles", Code: "required"})
}
if len(v.Roles) > 16 {
violations = append(violations, FieldViolation{Field: "roles", Code: "max_items"})
}
for _, item := range v.Roles {
if len(item) < 1 {
violations = append(violations, FieldViolation{Field: "roles", Code: "min_item_length"})
}
}
for _, item := range v.Roles {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "roles", Code: "max_item_length"})
}
}
for _, item := range v.Roles {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "roles", Code: "max_item_bytes"})
}
}
if v.Role == "" {
violations = append(violations, FieldViolation{Field: "role", Code: "required"})
}
if v.Role != "" && !(v.Role == "user" || v.Role == "admin") {
violations = append(violations, FieldViolation{Field: "role", Code: "invalid_value"})
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
return nil
}
func DecodeBrowserAuthenticatedSession(data []byte) (BrowserAuthenticatedSession, error) {
var value BrowserAuthenticatedSession
if len(data) > 1024*1024 {
return value, errors.New("protocol payload exceeds limit")
}
var fields map[string]json.RawMessage
if err := json.Unmarshal(data, &fields); err != nil {
return value, err
}
if raw, ok := fields["provider"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "provider", Code: "required"}}}
}
if raw, ok := fields["role"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "role", Code: "required"}}}
}
if raw, ok := fields["roles"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "roles", Code: "required"}}}
}
if raw, ok := fields["username"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "username", Code: "required"}}}
}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&value); err != nil {
return value, err
}
var trailing any
if err := decoder.Decode(&trailing); err != io.EOF {
if err == nil {
return value, errors.New("trailing JSON value")
}
return value, err
}
if err := value.Validate(); err != nil {
return value, err
}
return value, nil
}
func EncodeBrowserAuthenticatedSession(value BrowserAuthenticatedSession) ([]byte, error) {
if err := value.Validate(); err != nil {
return nil, err
}
return json.Marshal(value)
}
func (v CapabilityProfile) Validate() error {
var violations []FieldViolation
if v.Transport == "" {
@@ -2689,6 +2815,16 @@ func (v GatewayRegistration) Validate() error {
if len(v.Features) > 64 {
violations = append(violations, FieldViolation{Field: "features", Code: "max_items"})
}
for _, item := range v.Features {
if len(item) < 1 {
violations = append(violations, FieldViolation{Field: "features", Code: "min_item_length"})
}
}
for _, item := range v.Features {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "features", Code: "max_item_length"})
}
}
if reflect.DeepEqual(v.Capabilities, CapabilityProfile{}) {
violations = append(violations, FieldViolation{Field: "capabilities", Code: "required"})
}
@@ -3231,6 +3367,16 @@ func (v ManifestGateway) Validate() error {
if len(v.Addresses) > 4 {
violations = append(violations, FieldViolation{Field: "addresses", Code: "max_items"})
}
for _, item := range v.Addresses {
if len(item) < 1 {
violations = append(violations, FieldViolation{Field: "addresses", Code: "min_item_length"})
}
}
for _, item := range v.Addresses {
if len(item) > 256 {
violations = append(violations, FieldViolation{Field: "addresses", Code: "max_item_length"})
}
}
if v.PublicIdentity == "" {
violations = append(violations, FieldViolation{Field: "public_identity", Code: "required"})
}
@@ -3371,12 +3517,32 @@ func (v ManifestTunnel) Validate() error {
if len(v.Versions) > 4 {
violations = append(violations, FieldViolation{Field: "versions", Code: "max_items"})
}
for _, item := range v.Versions {
if len(item) < 1 {
violations = append(violations, FieldViolation{Field: "versions", Code: "min_item_length"})
}
}
for _, item := range v.Versions {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "versions", Code: "max_item_length"})
}
}
if v.Features == nil {
violations = append(violations, FieldViolation{Field: "features", Code: "required"})
}
if len(v.Features) > 32 {
violations = append(violations, FieldViolation{Field: "features", Code: "max_items"})
}
for _, item := range v.Features {
if len(item) < 1 {
violations = append(violations, FieldViolation{Field: "features", Code: "min_item_length"})
}
}
for _, item := range v.Features {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "features", Code: "max_item_length"})
}
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
@@ -3423,6 +3589,114 @@ func EncodeManifestTunnel(value ManifestTunnel) ([]byte, error) {
return json.Marshal(value)
}
func (v NativeAuthenticatedSession) Validate() error {
var violations []FieldViolation
if v.Username == "" {
violations = append(violations, FieldViolation{Field: "username", Code: "required"})
}
if len(v.Username) < 1 && v.Username != "" {
violations = append(violations, FieldViolation{Field: "username", Code: "min_length"})
}
if len(v.Username) > 256 {
violations = append(violations, FieldViolation{Field: "username", Code: "max_length"})
}
if v.Provider == "" {
violations = append(violations, FieldViolation{Field: "provider", Code: "required"})
}
if len(v.Provider) < 1 && v.Provider != "" {
violations = append(violations, FieldViolation{Field: "provider", Code: "min_length"})
}
if len(v.Provider) > 64 {
violations = append(violations, FieldViolation{Field: "provider", Code: "max_length"})
}
if v.Roles == nil {
violations = append(violations, FieldViolation{Field: "roles", Code: "required"})
}
if len(v.Roles) > 16 {
violations = append(violations, FieldViolation{Field: "roles", Code: "max_items"})
}
for _, item := range v.Roles {
if len(item) < 1 {
violations = append(violations, FieldViolation{Field: "roles", Code: "min_item_length"})
}
}
for _, item := range v.Roles {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "roles", Code: "max_item_length"})
}
}
for _, item := range v.Roles {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "roles", Code: "max_item_bytes"})
}
}
if v.Role == "" {
violations = append(violations, FieldViolation{Field: "role", Code: "required"})
}
if v.Role != "" && !(v.Role == "user" || v.Role == "admin") {
violations = append(violations, FieldViolation{Field: "role", Code: "invalid_value"})
}
if reflect.DeepEqual(v.NativeIdentity, NativeSessionIdentity{}) {
violations = append(violations, FieldViolation{Field: "native_identity", Code: "required"})
}
if err := v.NativeIdentity.Validate(); err != nil {
violations = append(violations, FieldViolation{Field: "native_identity", Code: "invalid_object"})
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
return nil
}
func DecodeNativeAuthenticatedSession(data []byte) (NativeAuthenticatedSession, error) {
var value NativeAuthenticatedSession
if len(data) > 1024*1024 {
return value, errors.New("protocol payload exceeds limit")
}
var fields map[string]json.RawMessage
if err := json.Unmarshal(data, &fields); err != nil {
return value, err
}
if raw, ok := fields["native_identity"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "native_identity", Code: "required"}}}
}
if raw, ok := fields["provider"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "provider", Code: "required"}}}
}
if raw, ok := fields["role"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "role", Code: "required"}}}
}
if raw, ok := fields["roles"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "roles", Code: "required"}}}
}
if raw, ok := fields["username"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "username", Code: "required"}}}
}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&value); err != nil {
return value, err
}
var trailing any
if err := decoder.Decode(&trailing); err != io.EOF {
if err == nil {
return value, errors.New("trailing JSON value")
}
return value, err
}
if err := value.Validate(); err != nil {
return value, err
}
return value, nil
}
func EncodeNativeAuthenticatedSession(value NativeAuthenticatedSession) ([]byte, error) {
if err := value.Validate(); err != nil {
return nil, err
}
return json.Marshal(value)
}
func (v NativeCredential) Validate() error {
var violations []FieldViolation
if len(v.DeviceID) > 128 {
@@ -3526,6 +3800,176 @@ func EncodeNativeCredential(value NativeCredential) ([]byte, error) {
return json.Marshal(value)
}
func (v NativeSessionIdentity) Validate() error {
var violations []FieldViolation
if v.ClientDeviceID == "" {
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "required"})
}
if len(v.ClientDeviceID) < 1 && v.ClientDeviceID != "" {
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "min_length"})
}
if len(v.ClientDeviceID) > 128 {
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "max_length"})
}
if v.DeviceKeyID == "" {
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "required"})
}
if len(v.DeviceKeyID) < 1 && v.DeviceKeyID != "" {
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "min_length"})
}
if len(v.DeviceKeyID) > 128 {
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "max_length"})
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
return nil
}
func DecodeNativeSessionIdentity(data []byte) (NativeSessionIdentity, error) {
var value NativeSessionIdentity
if len(data) > 1024*1024 {
return value, errors.New("protocol payload exceeds limit")
}
var fields map[string]json.RawMessage
if err := json.Unmarshal(data, &fields); err != nil {
return value, err
}
if raw, ok := fields["client_device_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "client_device_id", Code: "required"}}}
}
if raw, ok := fields["device_key_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "device_key_id", Code: "required"}}}
}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&value); err != nil {
return value, err
}
var trailing any
if err := decoder.Decode(&trailing); err != io.EOF {
if err == nil {
return value, errors.New("trailing JSON value")
}
return value, err
}
if err := value.Validate(); err != nil {
return value, err
}
return value, nil
}
func EncodeNativeSessionIdentity(value NativeSessionIdentity) ([]byte, error) {
if err := value.Validate(); err != nil {
return nil, err
}
return json.Marshal(value)
}
func (v NativeTunnelCredential) Validate() error {
var violations []FieldViolation
if v.ClientDeviceID == "" {
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "required"})
}
if len(v.ClientDeviceID) < 1 && v.ClientDeviceID != "" {
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "min_length"})
}
if len(v.ClientDeviceID) > 128 {
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "max_length"})
}
if v.DeviceKeyID == "" {
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "required"})
}
if len(v.DeviceKeyID) < 1 && v.DeviceKeyID != "" {
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "min_length"})
}
if len(v.DeviceKeyID) > 128 {
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "max_length"})
}
if v.CertificateChainPem == "" {
violations = append(violations, FieldViolation{Field: "certificate_chain_pem", Code: "required"})
}
if len(v.CertificateChainPem) < 1 && v.CertificateChainPem != "" {
violations = append(violations, FieldViolation{Field: "certificate_chain_pem", Code: "min_length"})
}
if len(v.CertificateChainPem) > 65536 {
violations = append(violations, FieldViolation{Field: "certificate_chain_pem", Code: "max_length"})
}
if v.TrustBundlePem == "" {
violations = append(violations, FieldViolation{Field: "trust_bundle_pem", Code: "required"})
}
if len(v.TrustBundlePem) < 1 && v.TrustBundlePem != "" {
violations = append(violations, FieldViolation{Field: "trust_bundle_pem", Code: "min_length"})
}
if len(v.TrustBundlePem) > 65536 {
violations = append(violations, FieldViolation{Field: "trust_bundle_pem", Code: "max_length"})
}
if v.ExpiresAt == "" {
violations = append(violations, FieldViolation{Field: "expires_at", Code: "required"})
}
if len(v.ExpiresAt) > 64 {
violations = append(violations, FieldViolation{Field: "expires_at", Code: "max_length"})
}
if v.ExpiresAt != "" {
if parsed, err := time.Parse(time.RFC3339Nano, v.ExpiresAt); err != nil || parsed.UTC().Format(time.RFC3339Nano) != v.ExpiresAt {
violations = append(violations, FieldViolation{Field: "expires_at", Code: "invalid_time"})
}
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
return nil
}
func DecodeNativeTunnelCredential(data []byte) (NativeTunnelCredential, error) {
var value NativeTunnelCredential
if len(data) > 1024*1024 {
return value, errors.New("protocol payload exceeds limit")
}
var fields map[string]json.RawMessage
if err := json.Unmarshal(data, &fields); err != nil {
return value, err
}
if raw, ok := fields["certificate_chain_pem"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "certificate_chain_pem", Code: "required"}}}
}
if raw, ok := fields["client_device_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "client_device_id", Code: "required"}}}
}
if raw, ok := fields["device_key_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "device_key_id", Code: "required"}}}
}
if raw, ok := fields["expires_at"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "expires_at", Code: "required"}}}
}
if raw, ok := fields["trust_bundle_pem"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "trust_bundle_pem", Code: "required"}}}
}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&value); err != nil {
return value, err
}
var trailing any
if err := decoder.Decode(&trailing); err != io.EOF {
if err == nil {
return value, errors.New("trailing JSON value")
}
return value, err
}
if err := value.Validate(); err != nil {
return value, err
}
return value, nil
}
func EncodeNativeTunnelCredential(value NativeTunnelCredential) ([]byte, error) {
if err := value.Validate(); err != nil {
return nil, err
}
return json.Marshal(value)
}
func (v PageInfo) Validate() error {
var violations []FieldViolation
if v.Limit == 0 {
@@ -3875,6 +4319,16 @@ func (v ProviderState) Validate() error {
if len(v.Channels) > 8 {
violations = append(violations, FieldViolation{Field: "channels", Code: "max_items"})
}
for _, item := range v.Channels {
if len(item) < 1 {
violations = append(violations, FieldViolation{Field: "channels", Code: "min_item_length"})
}
}
for _, item := range v.Channels {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "channels", Code: "max_item_length"})
}
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
@@ -4761,12 +5215,6 @@ func (v SessionRequest) Validate() error {
if len(v.IdempotencyKey) > 256 {
violations = append(violations, FieldViolation{Field: "idempotency_key", Code: "max_length"})
}
if reflect.DeepEqual(v.PolicySnapshot, AllocationPolicy{}) {
violations = append(violations, FieldViolation{Field: "policy_snapshot", Code: "required"})
}
if err := v.PolicySnapshot.Validate(); err != nil {
violations = append(violations, FieldViolation{Field: "policy_snapshot", Code: "invalid_object"})
}
if v.RequestedDisplayMode != nil {
if err := v.RequestedDisplayMode.Validate(); err != nil {
violations = append(violations, FieldViolation{Field: "requested_display_mode", Code: "invalid_object"})
@@ -4796,9 +5244,6 @@ func DecodeSessionRequest(data []byte) (SessionRequest, error) {
if raw, ok := fields["idempotency_key"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "idempotency_key", Code: "required"}}}
}
if raw, ok := fields["policy_snapshot"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "policy_snapshot", Code: "required"}}}
}
if raw, ok := fields["pool_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
return value, ValidationError{Violations: []FieldViolation{{Field: "pool_id", Code: "required"}}}
}
@@ -5057,12 +5502,22 @@ func (v VersionNegotiation) Validate() error {
if len(v.SupportedVersions) > 3 {
violations = append(violations, FieldViolation{Field: "supported_versions", Code: "max_items"})
}
for _, item := range v.SupportedVersions {
if len(item) > 16 {
violations = append(violations, FieldViolation{Field: "supported_versions", Code: "max_item_length"})
}
}
if v.Features == nil {
violations = append(violations, FieldViolation{Field: "features", Code: "required"})
}
if len(v.Features) > 64 {
violations = append(violations, FieldViolation{Field: "features", Code: "max_items"})
}
for _, item := range v.Features {
if len(item) > 64 {
violations = append(violations, FieldViolation{Field: "features", Code: "max_item_length"})
}
}
if len(violations) > 0 {
return ValidationError{Violations: violations}
}
+7 -7
View File
@@ -1,18 +1,18 @@
{
"compatibility": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"current": "1",
"current": "2",
"datagram_registry": "registries/datagrams.json",
"feature_registry": "registries/features.json",
"n_minus_1": "0",
"n_minus_2": "-1",
"n_minus_1": "1",
"n_minus_2": "0",
"protocol": "versevdi-control",
"unsupported": [
"-2",
"2"
"-1",
"3"
]
},
"generator_sha256": "992235a56d3467313148f86e47931f247591e4c8de737b55ac9c9eee35725fc5",
"generator_sha256": "8a153cf1e99682d010ff91c754ef056c64aece8f8bbca0ca58f8eef2b9039119",
"protocol_version": "1.0.0",
"schema_sha256": "b2bb0a8ac8ef56dbc0e1443eeb5b3028be9e71ec2f5fd8e73928d71b7cd9340c"
"schema_sha256": "dea3dd210c53d5a2d37050dd6afd8b0ac5bb8edcb7ab25a02e4026489ce8a00f"
}
+179 -9
View File
@@ -1,9 +1,9 @@
// Code generated by tools/generate.py; DO NOT EDIT.
#![allow(non_snake_case)]
pub const SCHEMA_SHA256: &str = "b2bb0a8ac8ef56dbc0e1443eeb5b3028be9e71ec2f5fd8e73928d71b7cd9340c";
pub const CURRENT_WIRE_VERSION: &str = "1";
pub const N_MINUS_1_WIRE_VERSION: &str = "0";
pub const N_MINUS_2_WIRE_VERSION: &str = "-1";
pub const SCHEMA_SHA256: &str = "dea3dd210c53d5a2d37050dd6afd8b0ac5bb8edcb7ab25a02e4026489ce8a00f";
pub const CURRENT_WIRE_VERSION: &str = "2";
pub const N_MINUS_1_WIRE_VERSION: &str = "1";
pub const N_MINUS_2_WIRE_VERSION: &str = "0";
pub type JsonObject = std::collections::BTreeMap<String, String>;
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -30,6 +30,19 @@ fn valid_base64_url(value: &str) -> bool {
_ => false,
}
}
fn valid_rfc3339_utc(value: &str) -> bool {
let bytes = value.as_bytes();
if bytes.len() < 20 || bytes.len() > 30 || bytes[4] != b'-' || bytes[7] != b'-' || bytes[10] != b'T' || bytes[13] != b':' || bytes[16] != b':' || *bytes.last().unwrap() != b'Z' { return false; }
let digits = |start: usize, end: usize| -> Option<u32> { bytes.get(start..end)?.iter().try_fold(0u32, |value, byte| if byte.is_ascii_digit() { Some(value * 10 + u32::from(*byte - b'0')) } else { None }) };
let (year, month, day, hour, minute, second) = match (digits(0, 4), digits(5, 7), digits(8, 10), digits(11, 13), digits(14, 16), digits(17, 19)) { (Some(year), Some(month), Some(day), Some(hour), Some(minute), Some(second)) => (year, month, day, hour, minute, second), _ => return false };
if hour > 23 || minute > 59 || second > 59 { return false; }
let leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
let days = match month { 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, 4 | 6 | 9 | 11 => 30, 2 if leap => 29, 2 => 28, _ => return false };
if day == 0 || day > days { return false; }
if bytes.len() == 20 { return true; }
let fraction = &bytes[20..bytes.len() - 1];
bytes[19] == b'.' && !fraction.is_empty() && fraction.len() <= 9 && fraction.iter().all(u8::is_ascii_digit) && *fraction.last().unwrap() != b'0'
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AllocationPolicy {
@@ -165,6 +178,7 @@ impl BrokerSession {
self.policySnapshot.validate().map_err(|_| ValidationError::new("policy_snapshot", "invalid_object"))?;
if let Some(value) = &self.reconnectDeadline {
if value.len() > 64 { return Err(ValidationError::new("reconnect_deadline", "max_length")); }
if !valid_rfc3339_utc(value.as_str()) { return Err(ValidationError::new("reconnect_deadline", "invalid_time")); }
}
if let Some(value) = &self.outcome {
if value.len() > 64 { return Err(ValidationError::new("outcome", "max_length")); }
@@ -182,8 +196,10 @@ impl BrokerSession {
if !self.correlationId.is_empty() && self.correlationId.len() < 1 { return Err(ValidationError::new("correlation_id", "min_length")); }
if self.correlationId.len() > 128 { return Err(ValidationError::new("correlation_id", "max_length")); }
if self.requestedAt.len() > 64 { return Err(ValidationError::new("requested_at", "max_length")); }
if !valid_rfc3339_utc(self.requestedAt.as_str()) { return Err(ValidationError::new("requested_at", "invalid_time")); }
if let Some(value) = &self.endedAt {
if value.len() > 64 { return Err(ValidationError::new("ended_at", "max_length")); }
if !valid_rfc3339_utc(value.as_str()) { return Err(ValidationError::new("ended_at", "invalid_time")); }
}
if self.version < 1 { return Err(ValidationError::new("version", "minimum")); }
if let Some(value) = &self.requestedDisplayMode {
@@ -213,6 +229,40 @@ impl BrokerSession {
pub fn effectiveDisplayMode(&self) -> &Option<DisplayMode> { &self.effectiveDisplayMode }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BrowserAuthenticatedSession {
username: String,
provider: String,
roles: Vec<String>,
role: String,
}
impl BrowserAuthenticatedSession {
pub fn new(username: String, provider: String, roles: Vec<String>, role: String) -> Result<Self, ValidationError> {
let value = Self { username, provider, roles, role };
value.validate()?;
Ok(value)
}
pub fn validate(&self) -> Result<(), ValidationError> {
if self.username.is_empty() { return Err(ValidationError::new("username", "required")); }
if !self.username.is_empty() && self.username.len() < 1 { return Err(ValidationError::new("username", "min_length")); }
if self.username.len() > 256 { return Err(ValidationError::new("username", "max_length")); }
if self.provider.is_empty() { return Err(ValidationError::new("provider", "required")); }
if !self.provider.is_empty() && self.provider.len() < 1 { return Err(ValidationError::new("provider", "min_length")); }
if self.provider.len() > 64 { return Err(ValidationError::new("provider", "max_length")); }
if self.roles.len() > 16 { return Err(ValidationError::new("roles", "max_items")); }
for item in self.roles.iter() { if item.as_bytes().len() < 1 { return Err(ValidationError::new("roles", "min_item_length")); } }
for item in self.roles.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("roles", "max_item_length")); } }
for item in self.roles.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("roles", "max_item_bytes")); } }
if self.role != "user" && self.role != "admin" { return Err(ValidationError::new("role", "invalid_value")); }
Ok(())
}
pub fn username(&self) -> &String { &self.username }
pub fn provider(&self) -> &String { &self.provider }
pub fn roles(&self) -> &Vec<String> { &self.roles }
pub fn role(&self) -> &String { &self.role }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CapabilityProfile {
transport: String,
@@ -428,6 +478,7 @@ impl DeviceChallenge {
if !self.challenge.is_empty() && self.challenge.len() < 1 { return Err(ValidationError::new("challenge", "min_length")); }
if self.challenge.len() > 256 { return Err(ValidationError::new("challenge", "max_length")); }
if self.expiresAt.len() > 64 { return Err(ValidationError::new("expires_at", "max_length")); }
if !valid_rfc3339_utc(self.expiresAt.as_str()) { return Err(ValidationError::new("expires_at", "invalid_time")); }
if self.algorithm != "ed25519" { return Err(ValidationError::new("algorithm", "invalid_value")); }
if self.signatureFormat != "ed25519-domain-separated-v1" { return Err(ValidationError::new("signature_format", "invalid_value")); }
Ok(())
@@ -636,6 +687,7 @@ impl EventEnvelope {
if self.version < 1 { return Err(ValidationError::new("version", "minimum")); }
self.resource.validate().map_err(|_| ValidationError::new("resource", "invalid_object"))?;
if self.occurredAt.len() > 64 { return Err(ValidationError::new("occurred_at", "max_length")); }
if !valid_rfc3339_utc(self.occurredAt.as_str()) { return Err(ValidationError::new("occurred_at", "invalid_time")); }
if self.correlationId.is_empty() { return Err(ValidationError::new("correlation_id", "required")); }
if !self.correlationId.is_empty() && self.correlationId.len() < 1 { return Err(ValidationError::new("correlation_id", "min_length")); }
if self.correlationId.len() > 128 { return Err(ValidationError::new("correlation_id", "max_length")); }
@@ -789,6 +841,7 @@ impl GatewayDrain {
if !self.reason.is_empty() && self.reason.len() < 1 { return Err(ValidationError::new("reason", "min_length")); }
if self.reason.len() > 256 { return Err(ValidationError::new("reason", "max_length")); }
if self.deadline.len() > 64 { return Err(ValidationError::new("deadline", "max_length")); }
if !valid_rfc3339_utc(self.deadline.as_str()) { return Err(ValidationError::new("deadline", "invalid_time")); }
Ok(())
}
pub fn version(&self) -> &String { &self.version }
@@ -823,6 +876,7 @@ impl GatewayHeartbeat {
if self.gatewayId.len() > 128 { return Err(ValidationError::new("gateway_id", "max_length")); }
if self.sequence < 1 { return Err(ValidationError::new("sequence", "minimum")); }
if self.observedAt.len() > 64 { return Err(ValidationError::new("observed_at", "max_length")); }
if !valid_rfc3339_utc(self.observedAt.as_str()) { return Err(ValidationError::new("observed_at", "invalid_time")); }
if self.activeConnections < 0 { return Err(ValidationError::new("active_connections", "minimum")); }
if self.activeConnections > 1000000 { return Err(ValidationError::new("active_connections", "maximum")); }
if self.egressKbps < 0 { return Err(ValidationError::new("egress_kbps", "minimum")); }
@@ -893,6 +947,8 @@ impl GatewayRegistration {
if self.bandwidthCapacityKbps < 1 { return Err(ValidationError::new("bandwidth_capacity_kbps", "minimum")); }
if self.bandwidthCapacityKbps > 1000000000 { return Err(ValidationError::new("bandwidth_capacity_kbps", "maximum")); }
if self.features.len() > 64 { return Err(ValidationError::new("features", "max_items")); }
for item in self.features.iter() { if item.as_bytes().len() < 1 { return Err(ValidationError::new("features", "min_item_length")); } }
for item in self.features.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("features", "max_item_length")); } }
self.capabilities.validate().map_err(|_| ValidationError::new("capabilities", "invalid_object"))?;
if self.protocolMinVersion > self.protocolMaxVersion { return Err(ValidationError::new("protocol_version", "invalid_order")); }
Ok(())
@@ -1016,6 +1072,7 @@ impl GrantReference {
if !self.opaqueValue.is_empty() && self.opaqueValue.len() < 43 { return Err(ValidationError::new("opaque_value", "min_length")); }
if self.opaqueValue.len() > 256 { return Err(ValidationError::new("opaque_value", "max_length")); }
if self.expiresAt.len() > 64 { return Err(ValidationError::new("expires_at", "max_length")); }
if !valid_rfc3339_utc(self.expiresAt.as_str()) { return Err(ValidationError::new("expires_at", "invalid_time")); }
if self.audience.is_empty() { return Err(ValidationError::new("audience", "required")); }
if !self.audience.is_empty() && self.audience.len() < 1 { return Err(ValidationError::new("audience", "min_length")); }
if self.audience.len() > 128 { return Err(ValidationError::new("audience", "max_length")); }
@@ -1103,6 +1160,8 @@ impl ManifestGateway {
if self.id.len() > 128 { return Err(ValidationError::new("id", "max_length")); }
if self.addresses.len() < 1 { return Err(ValidationError::new("addresses", "min_items")); }
if self.addresses.len() > 4 { return Err(ValidationError::new("addresses", "max_items")); }
for item in self.addresses.iter() { if item.as_bytes().len() < 1 { return Err(ValidationError::new("addresses", "min_item_length")); } }
for item in self.addresses.iter() { if item.as_bytes().len() > 256 { return Err(ValidationError::new("addresses", "max_item_length")); } }
if self.publicIdentity.is_empty() { return Err(ValidationError::new("public_identity", "required")); }
if !self.publicIdentity.is_empty() && self.publicIdentity.len() < 1 { return Err(ValidationError::new("public_identity", "min_length")); }
if self.publicIdentity.len() > 256 { return Err(ValidationError::new("public_identity", "max_length")); }
@@ -1156,13 +1215,54 @@ impl ManifestTunnel {
pub fn validate(&self) -> Result<(), ValidationError> {
if self.versions.len() < 1 { return Err(ValidationError::new("versions", "min_items")); }
if self.versions.len() > 4 { return Err(ValidationError::new("versions", "max_items")); }
for item in self.versions.iter() { if item.as_bytes().len() < 1 { return Err(ValidationError::new("versions", "min_item_length")); } }
for item in self.versions.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("versions", "max_item_length")); } }
if self.features.len() > 32 { return Err(ValidationError::new("features", "max_items")); }
for item in self.features.iter() { if item.as_bytes().len() < 1 { return Err(ValidationError::new("features", "min_item_length")); } }
for item in self.features.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("features", "max_item_length")); } }
Ok(())
}
pub fn versions(&self) -> &Vec<String> { &self.versions }
pub fn features(&self) -> &Vec<String> { &self.features }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NativeAuthenticatedSession {
username: String,
provider: String,
roles: Vec<String>,
role: String,
nativeIdentity: NativeSessionIdentity,
}
impl NativeAuthenticatedSession {
pub fn new(username: String, provider: String, roles: Vec<String>, role: String, nativeIdentity: NativeSessionIdentity) -> Result<Self, ValidationError> {
let value = Self { username, provider, roles, role, nativeIdentity };
value.validate()?;
Ok(value)
}
pub fn validate(&self) -> Result<(), ValidationError> {
if self.username.is_empty() { return Err(ValidationError::new("username", "required")); }
if !self.username.is_empty() && self.username.len() < 1 { return Err(ValidationError::new("username", "min_length")); }
if self.username.len() > 256 { return Err(ValidationError::new("username", "max_length")); }
if self.provider.is_empty() { return Err(ValidationError::new("provider", "required")); }
if !self.provider.is_empty() && self.provider.len() < 1 { return Err(ValidationError::new("provider", "min_length")); }
if self.provider.len() > 64 { return Err(ValidationError::new("provider", "max_length")); }
if self.roles.len() > 16 { return Err(ValidationError::new("roles", "max_items")); }
for item in self.roles.iter() { if item.as_bytes().len() < 1 { return Err(ValidationError::new("roles", "min_item_length")); } }
for item in self.roles.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("roles", "max_item_length")); } }
for item in self.roles.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("roles", "max_item_bytes")); } }
if self.role != "user" && self.role != "admin" { return Err(ValidationError::new("role", "invalid_value")); }
self.nativeIdentity.validate().map_err(|_| ValidationError::new("native_identity", "invalid_object"))?;
Ok(())
}
pub fn username(&self) -> &String { &self.username }
pub fn provider(&self) -> &String { &self.provider }
pub fn roles(&self) -> &Vec<String> { &self.roles }
pub fn role(&self) -> &String { &self.role }
pub fn nativeIdentity(&self) -> &NativeSessionIdentity { &self.nativeIdentity }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NativeCredential {
deviceId: Option<String>,
@@ -1193,8 +1293,10 @@ impl NativeCredential {
if !self.refreshToken.is_empty() && self.refreshToken.len() < 1 { return Err(ValidationError::new("refresh_token", "min_length")); }
if self.refreshToken.len() > 256 { return Err(ValidationError::new("refresh_token", "max_length")); }
if self.expiresAt.len() > 64 { return Err(ValidationError::new("expires_at", "max_length")); }
if !valid_rfc3339_utc(self.expiresAt.as_str()) { return Err(ValidationError::new("expires_at", "invalid_time")); }
if let Some(value) = &self.refreshExpiresAt {
if value.len() > 64 { return Err(ValidationError::new("refresh_expires_at", "max_length")); }
if !valid_rfc3339_utc(value.as_str()) { return Err(ValidationError::new("refresh_expires_at", "invalid_time")); }
}
Ok(())
}
@@ -1206,6 +1308,70 @@ impl NativeCredential {
pub fn refreshExpiresAt(&self) -> &Option<String> { &self.refreshExpiresAt }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NativeSessionIdentity {
clientDeviceId: String,
deviceKeyId: String,
}
impl NativeSessionIdentity {
pub fn new(clientDeviceId: String, deviceKeyId: String) -> Result<Self, ValidationError> {
let value = Self { clientDeviceId, deviceKeyId };
value.validate()?;
Ok(value)
}
pub fn validate(&self) -> Result<(), ValidationError> {
if self.clientDeviceId.is_empty() { return Err(ValidationError::new("client_device_id", "required")); }
if !self.clientDeviceId.is_empty() && self.clientDeviceId.len() < 1 { return Err(ValidationError::new("client_device_id", "min_length")); }
if self.clientDeviceId.len() > 128 { return Err(ValidationError::new("client_device_id", "max_length")); }
if self.deviceKeyId.is_empty() { return Err(ValidationError::new("device_key_id", "required")); }
if !self.deviceKeyId.is_empty() && self.deviceKeyId.len() < 1 { return Err(ValidationError::new("device_key_id", "min_length")); }
if self.deviceKeyId.len() > 128 { return Err(ValidationError::new("device_key_id", "max_length")); }
Ok(())
}
pub fn clientDeviceId(&self) -> &String { &self.clientDeviceId }
pub fn deviceKeyId(&self) -> &String { &self.deviceKeyId }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NativeTunnelCredential {
clientDeviceId: String,
deviceKeyId: String,
certificateChainPem: String,
trustBundlePem: String,
expiresAt: String,
}
impl NativeTunnelCredential {
pub fn new(clientDeviceId: String, deviceKeyId: String, certificateChainPem: String, trustBundlePem: String, expiresAt: String) -> Result<Self, ValidationError> {
let value = Self { clientDeviceId, deviceKeyId, certificateChainPem, trustBundlePem, expiresAt };
value.validate()?;
Ok(value)
}
pub fn validate(&self) -> Result<(), ValidationError> {
if self.clientDeviceId.is_empty() { return Err(ValidationError::new("client_device_id", "required")); }
if !self.clientDeviceId.is_empty() && self.clientDeviceId.len() < 1 { return Err(ValidationError::new("client_device_id", "min_length")); }
if self.clientDeviceId.len() > 128 { return Err(ValidationError::new("client_device_id", "max_length")); }
if self.deviceKeyId.is_empty() { return Err(ValidationError::new("device_key_id", "required")); }
if !self.deviceKeyId.is_empty() && self.deviceKeyId.len() < 1 { return Err(ValidationError::new("device_key_id", "min_length")); }
if self.deviceKeyId.len() > 128 { return Err(ValidationError::new("device_key_id", "max_length")); }
if self.certificateChainPem.is_empty() { return Err(ValidationError::new("certificate_chain_pem", "required")); }
if !self.certificateChainPem.is_empty() && self.certificateChainPem.len() < 1 { return Err(ValidationError::new("certificate_chain_pem", "min_length")); }
if self.certificateChainPem.len() > 65536 { return Err(ValidationError::new("certificate_chain_pem", "max_length")); }
if self.trustBundlePem.is_empty() { return Err(ValidationError::new("trust_bundle_pem", "required")); }
if !self.trustBundlePem.is_empty() && self.trustBundlePem.len() < 1 { return Err(ValidationError::new("trust_bundle_pem", "min_length")); }
if self.trustBundlePem.len() > 65536 { return Err(ValidationError::new("trust_bundle_pem", "max_length")); }
if self.expiresAt.len() > 64 { return Err(ValidationError::new("expires_at", "max_length")); }
if !valid_rfc3339_utc(self.expiresAt.as_str()) { return Err(ValidationError::new("expires_at", "invalid_time")); }
Ok(())
}
pub fn clientDeviceId(&self) -> &String { &self.clientDeviceId }
pub fn deviceKeyId(&self) -> &String { &self.deviceKeyId }
pub fn certificateChainPem(&self) -> &String { &self.certificateChainPem }
pub fn trustBundlePem(&self) -> &String { &self.trustBundlePem }
pub fn expiresAt(&self) -> &String { &self.expiresAt }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PageInfo {
limit: i64,
@@ -1268,6 +1434,7 @@ impl ProviderSessionWork {
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.expiresAt.len() > 64 { return Err(ValidationError::new("expires_at", "max_length")); }
if !valid_rfc3339_utc(self.expiresAt.as_str()) { return Err(ValidationError::new("expires_at", "invalid_time")); }
if self.providerProfile != "apollo" { return Err(ValidationError::new("provider_profile", "invalid_value")); }
if self.providerIdentity.is_empty() { return Err(ValidationError::new("provider_identity", "required")); }
if !self.providerIdentity.is_empty() && self.providerIdentity.len() < 1 { return Err(ValidationError::new("provider_identity", "min_length")); }
@@ -1348,6 +1515,8 @@ impl ProviderState {
if self.sessionId.len() > 128 { return Err(ValidationError::new("session_id", "max_length")); }
if self.state != "starting" && self.state != "ready" && self.state != "disconnected" && self.state != "terminating" && self.state != "terminated" && self.state != "cleanup_pending" && self.state != "failed" { return Err(ValidationError::new("state", "invalid_value")); }
if self.channels.len() > 8 { return Err(ValidationError::new("channels", "max_items")); }
for item in self.channels.iter() { if item.as_bytes().len() < 1 { return Err(ValidationError::new("channels", "min_item_length")); } }
for item in self.channels.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("channels", "max_item_length")); } }
Ok(())
}
pub fn version(&self) -> &String { &self.version }
@@ -1414,6 +1583,7 @@ impl ReauthGrant {
if !self.purpose.is_empty() && self.purpose.len() < 1 { return Err(ValidationError::new("purpose", "min_length")); }
if self.purpose.len() > 64 { return Err(ValidationError::new("purpose", "max_length")); }
if self.expiresAt.len() > 64 { return Err(ValidationError::new("expires_at", "max_length")); }
if !valid_rfc3339_utc(self.expiresAt.as_str()) { return Err(ValidationError::new("expires_at", "invalid_time")); }
Ok(())
}
pub fn token(&self) -> &String { &self.token }
@@ -1630,6 +1800,7 @@ impl SessionAuthority {
if self.audience.len() > 256 { return Err(ValidationError::new("audience", "max_length")); }
if self.reconnectSequence < 0 { return Err(ValidationError::new("reconnect_sequence", "minimum")); }
if self.expiresAt.len() > 64 { return Err(ValidationError::new("expires_at", "max_length")); }
if !valid_rfc3339_utc(self.expiresAt.as_str()) { return Err(ValidationError::new("expires_at", "invalid_time")); }
self.capabilities.validate().map_err(|_| ValidationError::new("capabilities", "invalid_object"))?;
if self.providerProfile != "apollo" { return Err(ValidationError::new("provider_profile", "invalid_value")); }
if self.providerIdentity.is_empty() { return Err(ValidationError::new("provider_identity", "required")); }
@@ -1654,13 +1825,12 @@ pub struct SessionRequest {
deviceKeyId: String,
poolId: String,
idempotencyKey: String,
policySnapshot: AllocationPolicy,
requestedDisplayMode: Option<DisplayMode>,
}
impl SessionRequest {
pub fn new(clientDeviceId: String, deviceKeyId: String, poolId: String, idempotencyKey: String, policySnapshot: AllocationPolicy, requestedDisplayMode: Option<DisplayMode>) -> Result<Self, ValidationError> {
let value = Self { clientDeviceId, deviceKeyId, poolId, idempotencyKey, policySnapshot, requestedDisplayMode };
pub fn new(clientDeviceId: String, deviceKeyId: String, poolId: String, idempotencyKey: String, requestedDisplayMode: Option<DisplayMode>) -> Result<Self, ValidationError> {
let value = Self { clientDeviceId, deviceKeyId, poolId, idempotencyKey, requestedDisplayMode };
value.validate()?;
Ok(value)
}
@@ -1677,7 +1847,6 @@ impl SessionRequest {
if self.idempotencyKey.is_empty() { return Err(ValidationError::new("idempotency_key", "required")); }
if !self.idempotencyKey.is_empty() && self.idempotencyKey.len() < 1 { return Err(ValidationError::new("idempotency_key", "min_length")); }
if self.idempotencyKey.len() > 256 { return Err(ValidationError::new("idempotency_key", "max_length")); }
self.policySnapshot.validate().map_err(|_| ValidationError::new("policy_snapshot", "invalid_object"))?;
if let Some(value) = &self.requestedDisplayMode {
value.validate().map_err(|_| ValidationError::new("requested_display_mode", "invalid_object"))?;
}
@@ -1687,7 +1856,6 @@ impl SessionRequest {
pub fn deviceKeyId(&self) -> &String { &self.deviceKeyId }
pub fn poolId(&self) -> &String { &self.poolId }
pub fn idempotencyKey(&self) -> &String { &self.idempotencyKey }
pub fn policySnapshot(&self) -> &AllocationPolicy { &self.policySnapshot }
pub fn requestedDisplayMode(&self) -> &Option<DisplayMode> { &self.requestedDisplayMode }
}
@@ -1799,7 +1967,9 @@ impl VersionNegotiation {
pub fn validate(&self) -> Result<(), ValidationError> {
if self.supportedVersions.len() < 1 { return Err(ValidationError::new("supported_versions", "min_items")); }
if self.supportedVersions.len() > 3 { return Err(ValidationError::new("supported_versions", "max_items")); }
for item in self.supportedVersions.iter() { if item.as_bytes().len() > 16 { return Err(ValidationError::new("supported_versions", "max_item_length")); } }
if self.features.len() > 64 { return Err(ValidationError::new("features", "max_items")); }
for item in self.features.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("features", "max_item_length")); } }
Ok(())
}
pub fn supportedVersions(&self) -> &Vec<String> { &self.supportedVersions }
+227 -23
View File
@@ -1,10 +1,10 @@
// Code generated by tools/generate.py; DO NOT EDIT.
import Foundation
public typealias JSONObject = [String: String]
public let schemaSHA256 = "b2bb0a8ac8ef56dbc0e1443eeb5b3028be9e71ec2f5fd8e73928d71b7cd9340c"
public let currentWireVersion = "1"
public let nMinus1WireVersion = "0"
public let nMinus2WireVersion = "-1"
public let schemaSHA256 = "dea3dd210c53d5a2d37050dd6afd8b0ac5bb8edcb7ab25a02e4026489ce8a00f"
public let currentWireVersion = "2"
public let nMinus1WireVersion = "1"
public let nMinus2WireVersion = "0"
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 func validBase64URL(_ value: String) -> Bool {
@@ -16,6 +16,23 @@ private func validBase64URL(_ value: String) -> Bool {
guard let decoded = Data(base64Encoded: standard) else { return false }
return decoded.base64EncodedString().replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "") == value
}
private func validRFC3339UTC(_ value: String) -> Bool {
let bytes = Array(value.utf8)
guard (20...30).contains(bytes.count), bytes[4] == 45, bytes[7] == 45, bytes[10] == 84, bytes[13] == 58, bytes[16] == 58, bytes.last == 90 else { return false }
func digits(_ range: Range<Int>) -> Int? {
var result = 0
for index in range { guard bytes[index] >= 48 && bytes[index] <= 57 else { return nil }; result = result * 10 + Int(bytes[index] - 48) }
return result
}
guard let year = digits(0..<4), let month = digits(5..<7), let day = digits(8..<10), let hour = digits(11..<13), let minute = digits(14..<16), let second = digits(17..<19), hour <= 23, minute <= 59, second <= 59 else { return false }
let leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
let days: Int
switch month { case 1, 3, 5, 7, 8, 10, 12: days = 31; case 4, 6, 9, 11: days = 30; case 2: days = leap ? 29 : 28; default: return false }
guard day > 0 && day <= days else { return false }
if bytes.count == 20 { return true }
let fraction = bytes[20..<(bytes.count - 1)]
return bytes[19] == 46 && !fraction.isEmpty && fraction.count <= 9 && fraction.allSatisfy { $0 >= 48 && $0 <= 57 } && fraction.last != 48
}
public struct AllocationPolicy: Codable, Equatable {
public let minimumKbps: Int64
@@ -217,7 +234,7 @@ public struct BrokerSession: Codable, Equatable {
try self.policySnapshot.validate()
if let value = self.reconnectDeadline {
if value.utf8.count > 64 { throw ContractValidationError(field: "reconnect_deadline", code: "max_length") }
if ISO8601DateFormatter().date(from: value) == nil { throw ContractValidationError(field: "reconnect_deadline", code: "invalid_time") }
if !validRFC3339UTC(value) { throw ContractValidationError(field: "reconnect_deadline", code: "invalid_time") }
}
if let value = self.outcome {
if value.utf8.count > 64 { throw ContractValidationError(field: "outcome", code: "max_length") }
@@ -235,10 +252,10 @@ public struct BrokerSession: Codable, Equatable {
if !self.correlationId.isEmpty && self.correlationId.utf8.count < 1 { throw ContractValidationError(field: "correlation_id", code: "min_length") }
if self.correlationId.utf8.count > 128 { throw ContractValidationError(field: "correlation_id", code: "max_length") }
if self.requestedAt.utf8.count > 64 { throw ContractValidationError(field: "requested_at", code: "max_length") }
if ISO8601DateFormatter().date(from: self.requestedAt) == nil { throw ContractValidationError(field: "requested_at", code: "invalid_time") }
if !validRFC3339UTC(self.requestedAt) { throw ContractValidationError(field: "requested_at", code: "invalid_time") }
if let value = self.endedAt {
if value.utf8.count > 64 { throw ContractValidationError(field: "ended_at", code: "max_length") }
if ISO8601DateFormatter().date(from: value) == nil { throw ContractValidationError(field: "ended_at", code: "invalid_time") }
if !validRFC3339UTC(value) { throw ContractValidationError(field: "ended_at", code: "invalid_time") }
}
if self.version < 1 { throw ContractValidationError(field: "version", code: "minimum") }
if let value = self.requestedDisplayMode {
@@ -253,6 +270,51 @@ public struct BrokerSession: Codable, Equatable {
public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }
}
public struct BrowserAuthenticatedSession: Codable, Equatable {
public let username: String
public let provider: String
public let roles: [String]
public let role: String
enum CodingKeys: String, CodingKey {
case username = "username"
case provider = "provider"
case roles = "roles"
case role = "role"
}
public init(username: String, provider: String, roles: [String], role: String) throws {
self.username = username
self.provider = provider
self.roles = roles
self.role = role
try validate()
}
public init(from decoder: Decoder) throws {
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") }
let c = try decoder.container(keyedBy: CodingKeys.self)
try self.init(username: try c.decode(String.self, forKey: .username), provider: try c.decode(String.self, forKey: .provider), roles: try c.decode([String].self, forKey: .roles), role: try c.decode(String.self, forKey: .role))
}
public func validate() throws {
if self.username.isEmpty { throw ContractValidationError(field: "username", code: "required") }
if !self.username.isEmpty && self.username.utf8.count < 1 { throw ContractValidationError(field: "username", code: "min_length") }
if self.username.utf8.count > 256 { throw ContractValidationError(field: "username", code: "max_length") }
if self.provider.isEmpty { throw ContractValidationError(field: "provider", code: "required") }
if !self.provider.isEmpty && self.provider.utf8.count < 1 { throw ContractValidationError(field: "provider", code: "min_length") }
if self.provider.utf8.count > 64 { throw ContractValidationError(field: "provider", code: "max_length") }
if self.roles.count > 16 { throw ContractValidationError(field: "roles", code: "max_items") }
for item in self.roles where item.utf8.count < 1 { throw ContractValidationError(field: "roles", code: "min_item_length") }
for item in self.roles where item.utf8.count > 64 { throw ContractValidationError(field: "roles", code: "max_item_length") }
for item in self.roles where item.utf8.count > 64 { throw ContractValidationError(field: "roles", code: "max_item_bytes") }
if !["user", "admin"].contains(self.role) { throw ContractValidationError(field: "role", code: "invalid_value") }
}
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }
}
public struct CapabilityProfile: Codable, Equatable {
public let transport: String
public let framing: String
@@ -551,7 +613,7 @@ public struct DeviceChallenge: Codable, Equatable {
if !self.challenge.isEmpty && self.challenge.utf8.count < 1 { throw ContractValidationError(field: "challenge", code: "min_length") }
if self.challenge.utf8.count > 256 { throw ContractValidationError(field: "challenge", code: "max_length") }
if self.expiresAt.utf8.count > 64 { throw ContractValidationError(field: "expires_at", code: "max_length") }
if ISO8601DateFormatter().date(from: self.expiresAt) == nil { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
if !validRFC3339UTC(self.expiresAt) { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
if self.algorithm != "ed25519" { throw ContractValidationError(field: "algorithm", code: "invalid_value") }
if self.signatureFormat != "ed25519-domain-separated-v1" { throw ContractValidationError(field: "signature_format", code: "invalid_value") }
}
@@ -831,7 +893,7 @@ public struct EventEnvelope: Codable, Equatable {
if self.version < 1 { throw ContractValidationError(field: "version", code: "minimum") }
try self.resource.validate()
if self.occurredAt.utf8.count > 64 { throw ContractValidationError(field: "occurred_at", code: "max_length") }
if ISO8601DateFormatter().date(from: self.occurredAt) == nil { throw ContractValidationError(field: "occurred_at", code: "invalid_time") }
if !validRFC3339UTC(self.occurredAt) { throw ContractValidationError(field: "occurred_at", code: "invalid_time") }
if self.correlationId.isEmpty { throw ContractValidationError(field: "correlation_id", code: "required") }
if !self.correlationId.isEmpty && self.correlationId.utf8.count < 1 { throw ContractValidationError(field: "correlation_id", code: "min_length") }
if self.correlationId.utf8.count > 128 { throw ContractValidationError(field: "correlation_id", code: "max_length") }
@@ -1036,7 +1098,7 @@ public struct GatewayDrain: Codable, Equatable {
if !self.reason.isEmpty && self.reason.utf8.count < 1 { throw ContractValidationError(field: "reason", code: "min_length") }
if self.reason.utf8.count > 256 { throw ContractValidationError(field: "reason", code: "max_length") }
if self.deadline.utf8.count > 64 { throw ContractValidationError(field: "deadline", code: "max_length") }
if ISO8601DateFormatter().date(from: self.deadline) == nil { throw ContractValidationError(field: "deadline", code: "invalid_time") }
if !validRFC3339UTC(self.deadline) { throw ContractValidationError(field: "deadline", code: "invalid_time") }
}
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
@@ -1089,7 +1151,7 @@ public struct GatewayHeartbeat: Codable, Equatable {
if self.gatewayId.utf8.count > 128 { throw ContractValidationError(field: "gateway_id", code: "max_length") }
if self.sequence < 1 { throw ContractValidationError(field: "sequence", code: "minimum") }
if self.observedAt.utf8.count > 64 { throw ContractValidationError(field: "observed_at", code: "max_length") }
if ISO8601DateFormatter().date(from: self.observedAt) == nil { throw ContractValidationError(field: "observed_at", code: "invalid_time") }
if !validRFC3339UTC(self.observedAt) { throw ContractValidationError(field: "observed_at", code: "invalid_time") }
if self.activeConnections < 0 { throw ContractValidationError(field: "active_connections", code: "minimum") }
if self.activeConnections > 1000000 { throw ContractValidationError(field: "active_connections", code: "maximum") }
if self.egressKbps < 0 { throw ContractValidationError(field: "egress_kbps", code: "minimum") }
@@ -1185,6 +1247,8 @@ public struct GatewayRegistration: Codable, Equatable {
if self.bandwidthCapacityKbps < 1 { throw ContractValidationError(field: "bandwidth_capacity_kbps", code: "minimum") }
if self.bandwidthCapacityKbps > 1000000000 { throw ContractValidationError(field: "bandwidth_capacity_kbps", code: "maximum") }
if self.features.count > 64 { throw ContractValidationError(field: "features", code: "max_items") }
for item in self.features where item.utf8.count < 1 { throw ContractValidationError(field: "features", code: "min_item_length") }
for item in self.features where item.utf8.count > 64 { throw ContractValidationError(field: "features", code: "max_item_length") }
try self.capabilities.validate()
if protocolMinVersion > protocolMaxVersion { throw ContractValidationError(field: "protocol_version", code: "invalid_order") }
}
@@ -1333,7 +1397,7 @@ public struct GrantReference: Codable, Equatable {
if !self.opaqueValue.isEmpty && self.opaqueValue.utf8.count < 43 { throw ContractValidationError(field: "opaque_value", code: "min_length") }
if self.opaqueValue.utf8.count > 256 { throw ContractValidationError(field: "opaque_value", code: "max_length") }
if self.expiresAt.utf8.count > 64 { throw ContractValidationError(field: "expires_at", code: "max_length") }
if ISO8601DateFormatter().date(from: self.expiresAt) == nil { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
if !validRFC3339UTC(self.expiresAt) { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
if self.audience.isEmpty { throw ContractValidationError(field: "audience", code: "required") }
if !self.audience.isEmpty && self.audience.utf8.count < 1 { throw ContractValidationError(field: "audience", code: "min_length") }
if self.audience.utf8.count > 128 { throw ContractValidationError(field: "audience", code: "max_length") }
@@ -1451,6 +1515,8 @@ public struct ManifestGateway: Codable, Equatable {
if self.id.utf8.count > 128 { throw ContractValidationError(field: "id", code: "max_length") }
if self.addresses.count < 1 { throw ContractValidationError(field: "addresses", code: "min_items") }
if self.addresses.count > 4 { throw ContractValidationError(field: "addresses", code: "max_items") }
for item in self.addresses where item.utf8.count < 1 { throw ContractValidationError(field: "addresses", code: "min_item_length") }
for item in self.addresses where item.utf8.count > 256 { throw ContractValidationError(field: "addresses", code: "max_item_length") }
if self.publicIdentity.isEmpty { throw ContractValidationError(field: "public_identity", code: "required") }
if !self.publicIdentity.isEmpty && self.publicIdentity.utf8.count < 1 { throw ContractValidationError(field: "public_identity", code: "min_length") }
if self.publicIdentity.utf8.count > 256 { throw ContractValidationError(field: "public_identity", code: "max_length") }
@@ -1522,7 +1588,60 @@ public struct ManifestTunnel: Codable, Equatable {
public func validate() throws {
if self.versions.count < 1 { throw ContractValidationError(field: "versions", code: "min_items") }
if self.versions.count > 4 { throw ContractValidationError(field: "versions", code: "max_items") }
for item in self.versions where item.utf8.count < 1 { throw ContractValidationError(field: "versions", code: "min_item_length") }
for item in self.versions where item.utf8.count > 64 { throw ContractValidationError(field: "versions", code: "max_item_length") }
if self.features.count > 32 { throw ContractValidationError(field: "features", code: "max_items") }
for item in self.features where item.utf8.count < 1 { throw ContractValidationError(field: "features", code: "min_item_length") }
for item in self.features where item.utf8.count > 64 { throw ContractValidationError(field: "features", code: "max_item_length") }
}
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }
}
public struct NativeAuthenticatedSession: Codable, Equatable {
public let username: String
public let provider: String
public let roles: [String]
public let role: String
public let nativeIdentity: NativeSessionIdentity
enum CodingKeys: String, CodingKey {
case username = "username"
case provider = "provider"
case roles = "roles"
case role = "role"
case nativeIdentity = "native_identity"
}
public init(username: String, provider: String, roles: [String], role: String, nativeIdentity: NativeSessionIdentity) throws {
self.username = username
self.provider = provider
self.roles = roles
self.role = role
self.nativeIdentity = nativeIdentity
try validate()
}
public init(from decoder: Decoder) throws {
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") }
let c = try decoder.container(keyedBy: CodingKeys.self)
try self.init(username: try c.decode(String.self, forKey: .username), provider: try c.decode(String.self, forKey: .provider), roles: try c.decode([String].self, forKey: .roles), role: try c.decode(String.self, forKey: .role), nativeIdentity: try c.decode(NativeSessionIdentity.self, forKey: .nativeIdentity))
}
public func validate() throws {
if self.username.isEmpty { throw ContractValidationError(field: "username", code: "required") }
if !self.username.isEmpty && self.username.utf8.count < 1 { throw ContractValidationError(field: "username", code: "min_length") }
if self.username.utf8.count > 256 { throw ContractValidationError(field: "username", code: "max_length") }
if self.provider.isEmpty { throw ContractValidationError(field: "provider", code: "required") }
if !self.provider.isEmpty && self.provider.utf8.count < 1 { throw ContractValidationError(field: "provider", code: "min_length") }
if self.provider.utf8.count > 64 { throw ContractValidationError(field: "provider", code: "max_length") }
if self.roles.count > 16 { throw ContractValidationError(field: "roles", code: "max_items") }
for item in self.roles where item.utf8.count < 1 { throw ContractValidationError(field: "roles", code: "min_item_length") }
for item in self.roles where item.utf8.count > 64 { throw ContractValidationError(field: "roles", code: "max_item_length") }
for item in self.roles where item.utf8.count > 64 { throw ContractValidationError(field: "roles", code: "max_item_bytes") }
if !["user", "admin"].contains(self.role) { throw ContractValidationError(field: "role", code: "invalid_value") }
try self.nativeIdentity.validate()
}
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
@@ -1576,10 +1695,10 @@ public struct NativeCredential: Codable, Equatable {
if !self.refreshToken.isEmpty && self.refreshToken.utf8.count < 1 { throw ContractValidationError(field: "refresh_token", code: "min_length") }
if self.refreshToken.utf8.count > 256 { throw ContractValidationError(field: "refresh_token", code: "max_length") }
if self.expiresAt.utf8.count > 64 { throw ContractValidationError(field: "expires_at", code: "max_length") }
if ISO8601DateFormatter().date(from: self.expiresAt) == nil { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
if !validRFC3339UTC(self.expiresAt) { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
if let value = self.refreshExpiresAt {
if value.utf8.count > 64 { throw ContractValidationError(field: "refresh_expires_at", code: "max_length") }
if ISO8601DateFormatter().date(from: value) == nil { throw ContractValidationError(field: "refresh_expires_at", code: "invalid_time") }
if !validRFC3339UTC(value) { throw ContractValidationError(field: "refresh_expires_at", code: "invalid_time") }
}
}
@@ -1587,6 +1706,91 @@ public struct NativeCredential: Codable, Equatable {
public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }
}
public struct NativeSessionIdentity: Codable, Equatable {
public let clientDeviceId: String
public let deviceKeyId: String
enum CodingKeys: String, CodingKey {
case clientDeviceId = "client_device_id"
case deviceKeyId = "device_key_id"
}
public init(clientDeviceId: String, deviceKeyId: String) throws {
self.clientDeviceId = clientDeviceId
self.deviceKeyId = deviceKeyId
try validate()
}
public init(from decoder: Decoder) throws {
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") }
let c = try decoder.container(keyedBy: CodingKeys.self)
try self.init(clientDeviceId: try c.decode(String.self, forKey: .clientDeviceId), deviceKeyId: try c.decode(String.self, forKey: .deviceKeyId))
}
public func validate() throws {
if self.clientDeviceId.isEmpty { throw ContractValidationError(field: "client_device_id", code: "required") }
if !self.clientDeviceId.isEmpty && self.clientDeviceId.utf8.count < 1 { throw ContractValidationError(field: "client_device_id", code: "min_length") }
if self.clientDeviceId.utf8.count > 128 { throw ContractValidationError(field: "client_device_id", code: "max_length") }
if self.deviceKeyId.isEmpty { throw ContractValidationError(field: "device_key_id", code: "required") }
if !self.deviceKeyId.isEmpty && self.deviceKeyId.utf8.count < 1 { throw ContractValidationError(field: "device_key_id", code: "min_length") }
if self.deviceKeyId.utf8.count > 128 { throw ContractValidationError(field: "device_key_id", code: "max_length") }
}
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }
}
public struct NativeTunnelCredential: Codable, Equatable {
public let clientDeviceId: String
public let deviceKeyId: String
public let certificateChainPem: String
public let trustBundlePem: String
public let expiresAt: String
enum CodingKeys: String, CodingKey {
case clientDeviceId = "client_device_id"
case deviceKeyId = "device_key_id"
case certificateChainPem = "certificate_chain_pem"
case trustBundlePem = "trust_bundle_pem"
case expiresAt = "expires_at"
}
public init(clientDeviceId: String, deviceKeyId: String, certificateChainPem: String, trustBundlePem: String, expiresAt: String) throws {
self.clientDeviceId = clientDeviceId
self.deviceKeyId = deviceKeyId
self.certificateChainPem = certificateChainPem
self.trustBundlePem = trustBundlePem
self.expiresAt = expiresAt
try validate()
}
public init(from decoder: Decoder) throws {
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") }
let c = try decoder.container(keyedBy: CodingKeys.self)
try self.init(clientDeviceId: try c.decode(String.self, forKey: .clientDeviceId), deviceKeyId: try c.decode(String.self, forKey: .deviceKeyId), certificateChainPem: try c.decode(String.self, forKey: .certificateChainPem), trustBundlePem: try c.decode(String.self, forKey: .trustBundlePem), expiresAt: try c.decode(String.self, forKey: .expiresAt))
}
public func validate() throws {
if self.clientDeviceId.isEmpty { throw ContractValidationError(field: "client_device_id", code: "required") }
if !self.clientDeviceId.isEmpty && self.clientDeviceId.utf8.count < 1 { throw ContractValidationError(field: "client_device_id", code: "min_length") }
if self.clientDeviceId.utf8.count > 128 { throw ContractValidationError(field: "client_device_id", code: "max_length") }
if self.deviceKeyId.isEmpty { throw ContractValidationError(field: "device_key_id", code: "required") }
if !self.deviceKeyId.isEmpty && self.deviceKeyId.utf8.count < 1 { throw ContractValidationError(field: "device_key_id", code: "min_length") }
if self.deviceKeyId.utf8.count > 128 { throw ContractValidationError(field: "device_key_id", code: "max_length") }
if self.certificateChainPem.isEmpty { throw ContractValidationError(field: "certificate_chain_pem", code: "required") }
if !self.certificateChainPem.isEmpty && self.certificateChainPem.utf8.count < 1 { throw ContractValidationError(field: "certificate_chain_pem", code: "min_length") }
if self.certificateChainPem.utf8.count > 65536 { throw ContractValidationError(field: "certificate_chain_pem", code: "max_length") }
if self.trustBundlePem.isEmpty { throw ContractValidationError(field: "trust_bundle_pem", code: "required") }
if !self.trustBundlePem.isEmpty && self.trustBundlePem.utf8.count < 1 { throw ContractValidationError(field: "trust_bundle_pem", code: "min_length") }
if self.trustBundlePem.utf8.count > 65536 { throw ContractValidationError(field: "trust_bundle_pem", code: "max_length") }
if self.expiresAt.utf8.count > 64 { throw ContractValidationError(field: "expires_at", code: "max_length") }
if !validRFC3339UTC(self.expiresAt) { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
}
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }
}
public struct PageInfo: Codable, Equatable {
public let limit: Int64
public let nextCursor: String
@@ -1703,7 +1907,7 @@ public struct ProviderSessionWork: Codable, Equatable {
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.expiresAt.utf8.count > 64 { throw ContractValidationError(field: "expires_at", code: "max_length") }
if ISO8601DateFormatter().date(from: self.expiresAt) == nil { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
if !validRFC3339UTC(self.expiresAt) { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
if self.providerProfile != "apollo" { throw ContractValidationError(field: "provider_profile", code: "invalid_value") }
if self.providerIdentity.isEmpty { throw ContractValidationError(field: "provider_identity", code: "required") }
if !self.providerIdentity.isEmpty && self.providerIdentity.utf8.count < 1 { throw ContractValidationError(field: "provider_identity", code: "min_length") }
@@ -1781,6 +1985,8 @@ public struct ProviderState: Codable, Equatable {
if self.sessionId.utf8.count > 128 { throw ContractValidationError(field: "session_id", code: "max_length") }
if !["starting", "ready", "disconnected", "terminating", "terminated", "cleanup_pending", "failed"].contains(self.state) { throw ContractValidationError(field: "state", code: "invalid_value") }
if self.channels.count > 8 { throw ContractValidationError(field: "channels", code: "max_items") }
for item in self.channels where item.utf8.count < 1 { throw ContractValidationError(field: "channels", code: "min_item_length") }
for item in self.channels where item.utf8.count > 64 { throw ContractValidationError(field: "channels", code: "max_item_length") }
}
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
@@ -1868,7 +2074,7 @@ public struct ReauthGrant: Codable, Equatable {
if !self.purpose.isEmpty && self.purpose.utf8.count < 1 { throw ContractValidationError(field: "purpose", code: "min_length") }
if self.purpose.utf8.count > 64 { throw ContractValidationError(field: "purpose", code: "max_length") }
if self.expiresAt.utf8.count > 64 { throw ContractValidationError(field: "expires_at", code: "max_length") }
if ISO8601DateFormatter().date(from: self.expiresAt) == nil { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
if !validRFC3339UTC(self.expiresAt) { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
}
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
@@ -2169,7 +2375,7 @@ public struct SessionAuthority: Codable, Equatable {
if self.audience.utf8.count > 256 { throw ContractValidationError(field: "audience", code: "max_length") }
if self.reconnectSequence < 0 { throw ContractValidationError(field: "reconnect_sequence", code: "minimum") }
if self.expiresAt.utf8.count > 64 { throw ContractValidationError(field: "expires_at", code: "max_length") }
if ISO8601DateFormatter().date(from: self.expiresAt) == nil { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
if !validRFC3339UTC(self.expiresAt) { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
try self.capabilities.validate()
if !["apollo"].contains(self.providerProfile) { throw ContractValidationError(field: "provider_profile", code: "invalid_value") }
if self.providerIdentity.isEmpty { throw ContractValidationError(field: "provider_identity", code: "required") }
@@ -2186,23 +2392,20 @@ public struct SessionRequest: Codable, Equatable {
public let deviceKeyId: String
public let poolId: String
public let idempotencyKey: String
public let policySnapshot: AllocationPolicy
public let requestedDisplayMode: DisplayMode?
enum CodingKeys: String, CodingKey {
case clientDeviceId = "client_device_id"
case deviceKeyId = "device_key_id"
case poolId = "pool_id"
case idempotencyKey = "idempotency_key"
case policySnapshot = "policy_snapshot"
case requestedDisplayMode = "requested_display_mode"
}
public init(clientDeviceId: String, deviceKeyId: String, poolId: String, idempotencyKey: String, policySnapshot: AllocationPolicy, requestedDisplayMode: DisplayMode?) throws {
public init(clientDeviceId: String, deviceKeyId: String, poolId: String, idempotencyKey: String, requestedDisplayMode: DisplayMode?) throws {
self.clientDeviceId = clientDeviceId
self.deviceKeyId = deviceKeyId
self.poolId = poolId
self.idempotencyKey = idempotencyKey
self.policySnapshot = policySnapshot
self.requestedDisplayMode = requestedDisplayMode
try validate()
}
@@ -2211,7 +2414,7 @@ public struct SessionRequest: Codable, Equatable {
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") }
let c = try decoder.container(keyedBy: CodingKeys.self)
try self.init(clientDeviceId: try c.decode(String.self, forKey: .clientDeviceId), deviceKeyId: try c.decode(String.self, forKey: .deviceKeyId), poolId: try c.decode(String.self, forKey: .poolId), idempotencyKey: try c.decode(String.self, forKey: .idempotencyKey), policySnapshot: try c.decode(AllocationPolicy.self, forKey: .policySnapshot), requestedDisplayMode: try c.contains(.requestedDisplayMode) ? c.decode(DisplayMode.self, forKey: .requestedDisplayMode) : nil)
try self.init(clientDeviceId: try c.decode(String.self, forKey: .clientDeviceId), deviceKeyId: try c.decode(String.self, forKey: .deviceKeyId), poolId: try c.decode(String.self, forKey: .poolId), idempotencyKey: try c.decode(String.self, forKey: .idempotencyKey), requestedDisplayMode: try c.contains(.requestedDisplayMode) ? c.decode(DisplayMode.self, forKey: .requestedDisplayMode) : nil)
}
public func validate() throws {
@@ -2227,7 +2430,6 @@ public struct SessionRequest: Codable, Equatable {
if self.idempotencyKey.isEmpty { throw ContractValidationError(field: "idempotency_key", code: "required") }
if !self.idempotencyKey.isEmpty && self.idempotencyKey.utf8.count < 1 { throw ContractValidationError(field: "idempotency_key", code: "min_length") }
if self.idempotencyKey.utf8.count > 256 { throw ContractValidationError(field: "idempotency_key", code: "max_length") }
try self.policySnapshot.validate()
if let value = self.requestedDisplayMode {
try value.validate()
}
@@ -2372,7 +2574,9 @@ public struct VersionNegotiation: Codable, Equatable {
public func validate() throws {
if self.supportedVersions.count < 1 { throw ContractValidationError(field: "supported_versions", code: "min_items") }
if self.supportedVersions.count > 3 { throw ContractValidationError(field: "supported_versions", code: "max_items") }
for item in self.supportedVersions where item.utf8.count > 16 { throw ContractValidationError(field: "supported_versions", code: "max_item_length") }
if self.features.count > 64 { throw ContractValidationError(field: "features", code: "max_items") }
for item in self.features where item.utf8.count > 64 { throw ContractValidationError(field: "features", code: "max_item_length") }
}
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }